Most OpenClaw demos show a single channel—usually Telegram—because it’s the path of least resistance. But real-world teams need the same agent reachable everywhere users already hang out. This article walks through a full OpenClaw multi-channel setup connecting all messaging apps at once: WhatsApp, Telegram, and Discord. We’ll install the latest gateway, add three connectors, define routing rules, tweak per-channel behavior, and handle the edge case where the same human pings the bot from different platforms.
Why bother with a multi-channel OpenClaw setup?
Teams rarely agree on a single chat platform. Customer support happens on WhatsApp, community on Discord, internal alerts on Telegram. Running separate agents per channel doubles infrastructure and triples cognitive load. With OpenClaw 0.37.0 the gateway can multiplex inbound webhooks and fan out responses through the daemon, so one container is enough. Less state drift, one memory store, and zero duplicated prompt tuning.
Prerequisites and version sanity check
- Node.js 22.1+. v20 technically runs but you’ll fight with ES module quirks.
- OpenClaw gateway 0.37.0 or newer (
openclaw-gateway --version) - Daemon 0.22.3+ for stable memory persistence.
- PostgreSQL or SQLite – we’ll use SQLite for brevity.
- API credentials:
- WhatsApp Cloud API token + phone ID
- Telegram BotFather token
- Discord bot token + application ID
- Public URL for webhooks (ngrok, Cloudflare Tunnel, or ClawCloud).
Installing and bootstrapping OpenClaw
Skip if you already have a running gateway. Otherwise:
# macOS/Linux
npm install -g openclaw-gateway@0.37.0 openclaw-daemon@0.22.3
# initialise a new project
mkdir multi-channel-bot && cd $_
openclaw init
# accepts defaults, chooses SQLite
This creates gateway.config.json, daemon.config.json, and a prompts/ folder. Out of the box the config only has a dummy Console connector.
Adding WhatsApp, Telegram and Discord connectors
The gateway treats each messaging app as a “transport”. Transports live under gateway.config.json.transports. We’ll add three blocks plus a fourth one—the internal router—that decides where to send replies.
1. WhatsApp Cloud API
{
"id": "whatsapp",
"module": "@openclaw/transport-whatsapp",
"config": {
"token": "WHATSAPP_CLOUD_TOKEN",
"phoneId": "1234567890",
"verifyToken": "my-webhook-verify",
"webhookPath": "/webhook/whatsapp"
}
}
Add the verify token inside Meta’s UI, point the webhook URL to https://your-public-url/webhook/whatsapp and select all message events.
2. Telegram
{
"id": "telegram",
"module": "@openclaw/transport-telegram",
"config": {
"botToken": "TELEGRAM_BOT_TOKEN",
"webhookPath": "/webhook/telegram"
}
}
Telegram supports long polling or webhooks. Webhooks are less CPU hungry, so we pass --webhook when starting later.
3. Discord
{
"id": "discord",
"module": "@openclaw/transport-discord",
"config": {
"botToken": "DISCORD_BOT_TOKEN",
"appId": "DISCORD_APP_ID",
"publicKey": "DISCORD_PUBLIC_KEY"
}
}
Discord’s signature verification happens inside the module, just remember to give the bot Message Content Intent.
4. Glue everything in gateway.config.json
{
"transports": [
"./transports/whatsapp.json",
"./transports/telegram.json",
"./transports/discord.json"
],
"router": "./router.js",
"defaultAgent": "support-agent"
}
We extract the router into plain JS so we can write real logic instead of YAML conditionals.
Routing rules: one function to rule them all
The router receives a normalized event object:
{
channel: 'telegram' | 'whatsapp' | 'discord',
senderId: 'platform-native-id',
message: {
text: 'string',
attachments: [...]
},
context: { ... }
}
Sample router.js that fans out to different prompts and moderation:
module.exports = async function route(event, { agents, memory }) {
// Edge-case: same user across channels, map via memory
const canonicalUserId = await upsertCrossChannelIdentity(event, memory);
// Basic profanity guard, only on public Discord channels
if (event.channel === 'discord' && isPublicDiscord(event)) {
if (containsBannedWords(event.message.text)) return { drop: true };
}
// Pick agent based on channel
const agentId = {
telegram: 'support-agent',
whatsapp: 'support-agent',
discord: 'community-agent'
}[event.channel];
return {
agent: agents[agentId],
memoryKey: canonicalUserId
};
};
Key detail: we unify user identities (see next section) so the agent remembers context across apps.
Stitching the same user across different platforms
OpenClaw keeps memories per memoryKey. If you don’t normalise, the agent forgets everything when the user hops from WhatsApp to Discord. A naïve fix is using phone numbers, but Discord doesn’t expose those.
The pragmatic approach is an ask-once fallback. When the agent sees a new platform ID it asks the user for an @username or email, then stores a mapping table:
// memory namespace: crossChannelUsers
{
"c6f12e-telegram": {
email: "jane@example.com",
platforms: ["telegram:c6f12e", "discord:19191", "whatsapp:+4917012345"]
}
}
Later lookups reuse the same crossChannelUsers record. With SQLite it’s a jsonb column; on Postgres use a separate table if you care about SQL joins.
Per-channel behavior differences
WhatsApp users expect shorter answers, Discord users love markdown, Telegram supports buttons. Instead of forking prompts, override the formatters per channel.
// gateway.config.json (excerpt)
"formatters": {
"telegram": "@openclaw/formatter-telegram-rich",
"whatsapp": "@openclaw/formatter-whatsapp-concise",
"discord": "@openclaw/formatter-discord-md"
}
Now the agent sends the same semantic answer but rendered differently.
Limiting token usage on WhatsApp
Meta charges per message. The WhatsApp formatter truncates to 1000 characters and appends ... (continued on Telegram) with a t.me link if longer. Community has PR #2457 improving this behaviour; merge expected in 0.38.
Running everything locally
export PUBLIC_URL="https://your-tunnel.ngrok-free.app"
openclaw-daemon start & # background
openclaw-gateway start --webhook --port 8080 --url $PUBLIC_URL
Send “ping” from all three apps—you should get “pong” back.
Deploying to ClawCloud instead
If you don’t want to babysit processes, push to ClawCloud:
- Sign in → New Agent → pick “Import existing repo”.
- Set environment variables (the three tokens + DB_URL).
- Select the “multi-channel” template or just paste your
gateway.config.json. - ClawCloud generates HTTPS webhook endpoints automatically; copy them back into Meta/Twitter/Discord if needed.
The deployment is zero-ops. Under the hood ClawCloud runs the daemon as a sidecar and manages TLS renewals. Cold starts average 300ms on the free tier, so Telegram’s 10-second timeout is never hit.
Troubleshooting checklist
- WhatsApp 400 Bad Request: usually missing
Content-Type: application/jsonheader in Meta config. Re-save the webhook. - Telegram “hook already set”: run
curl -X POST https://api.telegram.org/bot$TOKEN/deleteWebhookbefore re-registering. - Discord 401 signature mismatch: double-check you pasted the public key, not client secret.
- Memory not shared: confirm both gateway and daemon point to the same
OPENCLAW_DBURL. - Duplicate replies: you started two gateway instances. Bind only one to the webhook port or use ClawCloud’s scaling limiter.
Next steps
Your agent now lives simultaneously on WhatsApp, Telegram and Discord with shared memory and transport-aware formatting. From here you can:
- Add Signal or Slack by dropping their connector JSON into
transports/. - Write deeper routing logic—e.g. funnel “/shell” commands to an ops-specific agent.
- Open a PR improving the cross-channel identity helper; everyone complains about it in GitHub Discussion #982.
Multi-channel isn’t hard, but it’s picky about webhooks and tokens. Lock those down, and one OpenClaw instance will happily serve all your chats.