🛠️

Build for Odyssey

Extend Odyssey with your own service. Connect a new microservice and it becomes accessible through WhatsApp in minutes.

Architecture

Odyssey uses a webhook-based architecture. Your service receives messages via HTTP and replies through a simple egress API.

👤
User WhatsApp message
🧭
Odyssey Routes to your app
📦
Your Service Processes request
📤
Egress API POST /send
💬
WhatsApp User sees reply

The Contract

Two things matter: what Odyssey sends you, and how you reply back.

📥

Ingress: What you receive

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"
}
from - phone number (no + prefix)
raw_text - the original WhatsApp message
intent - "/task", "/keep", "continuation", or AI-generated
app - your app key (e.g. "liaw")
entities - AI-extracted key/value pairs (empty for explicit commands)
push_name - WhatsApp display name (optional)
Auth: If you set a webhook_secret, Odyssey sends it as x-gateway-secret header.
📤

Egress: How you reply

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! 🎯"
}
to - phone number (same as from in ingress)
text - plain text message to send back
Responses: 200 OK, 400 (missing fields), 401 (bad API key), 503 (WhatsApp disconnected).

Step-by-step guide

Add a new downstream service to Odyssey in 7 steps.

1

Add environment variables

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(),
2

Register your app

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,
  },
},
3

Map commands (optional)

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.

4

Set up Docker network

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
5

Build your service

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'));
6

Configure .env

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
7

Run and test

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.

AI routing: zero extra work

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.