Extend Odyssey with your own service. Connect a new microservice and it becomes accessible through WhatsApp in minutes.
Odyssey uses a webhook-based architecture. Your service receives messages via HTTP and replies through a simple egress API.
Two things matter: what Odyssey sends you, and how you reply back.
Odyssey sends a POST to your webhook URL with this payload:
{
"from": "918082446387",
"raw_text": "Buy groceries",
"intent": "/task",
"app": "liaw",
"entities": {},
"timestamp": "2026-09-10T14:30:00Z",
"push_name": "Kaushal"
}
webhook_secret, Odyssey sends it as x-gateway-secret header.
Send a POST to Odyssey's egress API:
POST http://odyssey:3000/send
Headers:
Content-Type: application/json
x-api-key: YOUR_GATEWAY_API_KEY
Body:
{
"to": "918082446387",
"text": "Task saved! 🎯"
}
from in ingress)Add a new downstream service to Odyssey in 7 steps.
In src/types/env.ts, add your app's URL and optional secret to the Zod schema.
MY_APP_WEBHOOK_URL: z.string().url(),
MY_APP_WEBHOOK_SECRET: z.string().optional(),
In src/config/index.ts, add your app to the apps map:
apps: {
// ... existing apps
myapp: {
webhook_url: env.MY_APP_WEBHOOK_URL,
description: 'Your app description (used in /help and AI routing)',
webhook_secret: env.MY_APP_WEBHOOK_SECRET,
},
},
Add slash-commands that route directly to your app in the explicit_commands map:
explicit_commands: {
// ... existing commands
'/my-cmd': 'myapp',
},
Skip this if you only want AI-based routing. The Gemini classifier auto-discovers your app from the apps map.
Create a Docker network and add it to docker-compose.yml:
# Create the network
docker network create myapp
# Add to docker-compose.yml under networks:
myapp:
external: true
# Add to odyssey service's networks list:
networks:
- default
- liaw-shared
- keep
- myapp # ← add here
Create an HTTP service that handles the webhook. Here's a minimal Express example:
import express from 'express';
import axios from 'axios';
const app = express();
app.use(express.json());
// Your webhook endpoint
app.post('/webhook', async (req, res) => {
const { from, raw_text, intent, entities } = req.body;
// Verify gateway secret (optional but recommended)
const secret = req.headers['x-gateway-secret'];
if (secret !== process.env.GATEWAY_SECRET) {
return res.status(401).json({ error: 'Unauthorized' });
}
// Process the message...
const reply = handleMessage(raw_text, entities);
// Reply via Odyssey's egress API
await axios.post('http://odyssey:3000/send', {
to: from,
text: reply,
}, {
headers: {
'x-api-key': process.env.GATEWAY_API_KEY,
'Content-Type': 'application/json',
},
});
res.json({ status: 'ok' });
});
app.listen(8080, () => console.log('Service running on :8080'));
Add your service's values to .env:
MY_APP_WEBHOOK_URL=http://myapp-app:8080/webhook
MY_APP_WEBHOOK_SECRET=your-shared-secret-here
GATEWAY_API_KEY=your-gateway-key-here
Start everything with Docker Compose:
docker-compose up -d
Send a message to your WhatsApp number. Odyssey will route it to your service, and you'll get a reply back.
Once your app is registered in the apps map, the Gemini AI classifier automatically discovers it. The classifier builds its prompt from the app registry at startup. Your description becomes the routing hint.
This means users can message naturally ("save this link", "create a task", "remind me later") and Odyssey routes correctly without explicit commands. The AI extracts intent and entities, then forwards them in the webhook payload.
Tip: Write a clear description for your app. The classifier uses it to decide when your service is the best match. Be specific about what your app does.