Channels
Intern receives tasks and sends replies over a channel — a chat platform you already use. At least one channel is required during setup. Telegram is the recommended default because the bot setup is the simplest.
The captive-portal wizard sets exactly one channel (the value in the channel field of POST /api/device/setup) and only accepts telegram, slack, or discord. WhatsApp is not selectable in the wizard because it requires an interactive QR-code pairing flow with the WhatsApp mobile app — it can only be added after the device is paired, via the MQTT-backed POST /api/device/channel endpoint. See WhatsApp (post-pairing only) and Adding more channels below.
Required setup fields by channel
These are the fields validated by lib/openclaw/domain/setup.go (SetupRequest.ValidateChannel). The wizard collects them; all are mandatory when the matching channel value is selected.
channel | Available in wizard? | Required fields |
|---|---|---|
telegram (default) | ✅ yes | telegram_bot_token, telegram_user_id |
slack | ✅ yes | slack_bot_token, slack_app_token, slack_user_id |
discord | ✅ yes | discord_bot_token, discord_guild_id, discord_user_id |
whatsapp | ❌ no — post-pairing only | whatsapp_user_id (E.164 phone number, e.g. +15551234567) — added via POST /api/device/channel |
Telegram (recommended)
Create a bot
- Open Telegram and message @BotFather.
- Send
/newbot, follow the prompts, choose a name and username. - BotFather replies with a bot token that looks like
123456789:ABC-DEF1234ghIkl-.... - Send
/startto your new bot, then visithttps://api.telegram.org/bot<TOKEN>/getUpdatesin a browser. The JSON response contains your numericchat.id— that's yourtelegram_user_id.
Enter in the wizard
| Field | Value |
|---|---|
| Channel | telegram |
| Bot token | the string from BotFather |
| User ID | your numeric chat.id from getUpdates |
intern-server writes these to /root/config/config.json:
{
"telegram_bot_token": "...",
"telegram_user_id": "..."
}
They're then merged into /root/.openclaw/openclaw.json under channels.telegram:
{
"channels": {
"telegram": {
"enabled": true,
"botToken": "...",
"dmPolicy": "allowlist",
"allowFrom": ["<your-telegram_user_id>"]
}
}
}
dmPolicy: "allowlist" with a populated allowFrom is what restricts your bot to responding only to you. Set allowFrom to ["*"] and dmPolicy: "open" if you want anyone who finds the bot to be able to message it (not recommended).
Slack
Slack requires two tokens because the gateway runs in Socket Mode:
- Bot token (
xoxb-…) — issued when you install the app to a workspace - App-level token (
xapp-…) — issued under "Basic Information" → "App-Level Tokens" with theconnections:writescope
Set up the app:
- Create a Slack app at api.slack.com/apps → From scratch.
- Under OAuth & Permissions, grant the bot the scopes Slack requires for the features you want (Slack's docs are the source of truth for the exact scope list; check
lib/openclaw/openclaw.goapplySlackChannelConfigfor the channel's runtime requirements). - Install the app to your workspace; copy the Bot User OAuth Token (
xoxb-…). - Under Basic Information → App-Level Tokens, create one with scope
connections:write(xapp-…). - Enable Socket Mode.
- Get your Slack user ID by clicking your profile → "More" → "Copy member ID" (it starts with
U).
In the wizard, pick slack and paste all three. The merged channels.slack block sets mode: "socket", botToken, appToken, and the canonical channel options.
Discord
Discord requires:
- A bot token from the Discord Developer Portal
- A guild ID (server ID) that the bot has joined
- A user ID (your Discord numeric ID, not your username)
Set up the app:
- Discord Developer Portal → New Application → Bot → Reset Token.
- Enable Message Content Intent (and any other intents you need).
- Invite the bot to your server with the OAuth2 URL Generator —
botscope, plus message permissions. - Enable Developer Mode in Discord (User Settings → Advanced), then right-click your server → "Copy Server ID" (guild ID), and right-click your name → "Copy User ID".
In the wizard, pick discord and paste the bot token, guild ID, and user ID.
WhatsApp (post-pairing only)
WhatsApp is not a valid value for the captive-portal wizard's channel field (SetupRequest.EffectiveChannel intentionally rejects it). It can only be attached after the device is paired and set_up_completed: true, by issuing an MQTT command to the device.
Requirements
- The device must already be paired via Telegram, Slack, or Discord and be online on Wi-Fi.
- A WhatsApp account on your phone — your everyday personal account is fine. The Intern uses WhatsApp's standard Linked Devices feature (the same one that powers WhatsApp Web / Desktop), so any account that can scan a QR works.
- The Intern runs OpenClaw (the default). WhatsApp is not supported on the Hermes agent —
/api/device/setuprejects WhatsApp creds whenactive_agent=hermes. See Setup Hermes → Overview.
Required field
| Field | Format | What it controls |
|---|---|---|
whatsapp_user_id | E.164 phone number, e.g. +15551234567 | The phone number allowed to DM the device. The simplest setup is to put your own number here and use WhatsApp's "Message Yourself" chat — you send a task into that chat, the linked Intern reads it and replies in the same chat. |
Pairing flow
The device exposes WhatsApp pairing as a streaming MQTT exchange — backend sends one add_channel command, the device replies with many events until pairing succeeds, times out, or fails.
- Backend publishes on the device's
fa_channeltopic:{ "cmd": "add_channel", "channel": "whatsapp", "config": { "user_id": "+15551234567" } } - Device runs
openclaw channels add --channel whatsapp, overlaysdmPolicy: "allowlist"+allowFrom: ["+15551234567"]intoopenclaw.json, enables the WhatsApp plugin (openclaw plugins enable whatsapp), and restarts the gateway. - Device streams events on
fd_channel(one MQTT publish per event):status: "pairing_starting"status: "pairing_qr"withpairing_qr_text(Unicode-block QR),pairing_qr_seq(rotates ~every 20s; backend should discard older sequences), andpairing_expires_atstatus: "success"once the device has saved Baileys credentials, orstatus: "timeout" | "failure"on error
- On your phone, open WhatsApp → Settings → Linked Devices → Link a Device and scan the QR rendered by the backend from
pairing_qr_text. - On success, the CLI prints
✅ Linked after restart; web session ready.and the device publishesstatus: "success". Any message from a number inallowFromis then routed to the LLM — if you put your own number there, just open the Message Yourself chat in WhatsApp and start typing.
If a session is already saved at <openclaw_config_dir>/credentials/whatsapp/default/creds.json, the device publishes status: "success" immediately and skips the QR step — Baileys auto-reconnects. (Fresh pairings and resumed sessions now share the same terminal status; the older session_resumed status has been retired to keep the wire vocabulary minimal — see app/intern/internal/device/service.go.)
Re-pairing after session loss
If the WhatsApp session dies (you tapped Log out on the phone's Linked Devices screen, the phone's SIM was offline for 14+ days, or someone ran openclaw channels logout on the device), the backend re-runs the flow with:
{ "cmd": "whatsapp_pair" }
No config payload is needed — the device reuses the previously-stored whatsapp_user_id and streams a fresh QR. Reboots, gateway crashes, and OS restarts do not lose the session (Baileys auto-reconnects); factory reset (10s long-press) does, because it wipes /root/.openclaw.
Adding more channels
Post-setup, additional channels are configured via the MQTT-backed POST /api/device/channel endpoint, which takes the same per-channel fields plus channel: "telegram" | "slack" | "discord" | "whatsapp". WhatsApp is only addable this way — it's not a valid value for the initial setup wizard's channel field.
Channel tokens at rest
All channel tokens are stored plaintext in /root/config/config.json and in the canonical channel blocks under channels.{telegram,slack,discord} in /root/.openclaw/openclaw.json. Both files live on the device — none of these tokens are uploaded to Autonomous backend servers. Protect physical access to the device; use a dedicated bot account per channel; and rotate the tokens through the wizard / channel endpoint if you suspect they've leaked.