diff --git a/docs/channels/whatsapp.md b/docs/channels/whatsapp.md index 0586c5ad17d..23bbb38f747 100644 --- a/docs/channels/whatsapp.md +++ b/docs/channels/whatsapp.md @@ -1,406 +1,434 @@ --- -summary: "WhatsApp (web channel) integration: login, inbox, replies, media, and ops" +summary: "WhatsApp channel support, access controls, delivery behavior, and operations" read_when: - Working on WhatsApp/web channel behavior or inbox routing title: "WhatsApp" --- -# WhatsApp (web channel) +# WhatsApp (Web channel) -Status: WhatsApp Web via Baileys only. Gateway owns the session(s). +Status: production-ready via WhatsApp Web (Baileys). Gateway owns linked session(s). -## Quick setup (beginner) + + + Default DM policy is pairing for unknown senders. + + + Cross-channel diagnostics and repair playbooks. + + + Full channel config patterns and examples. + + -1. Use a **separate phone number** if possible (recommended). -2. Configure WhatsApp in `~/.openclaw/openclaw.json`. -3. Run `openclaw channels login` to scan the QR code (Linked Devices). -4. Start the gateway. +## Quick setup -Minimal config: + + ```json5 { channels: { whatsapp: { - dmPolicy: "allowlist", + dmPolicy: "pairing", allowFrom: ["+15551234567"], + groupPolicy: "allowlist", + groupAllowFrom: ["+15551234567"], }, }, } ``` -## Goals + -- Multiple WhatsApp accounts (multi-account) in one Gateway process. -- Deterministic routing: replies return to WhatsApp, no model routing. -- Model sees enough context to understand quoted replies. + -## Config writes - -By default, WhatsApp is allowed to write config updates triggered by `/config set|unset` (requires `commands.config: true`). - -Disable with: - -```json5 -{ - channels: { whatsapp: { configWrites: false } }, -} +```bash +openclaw channels login --channel whatsapp ``` -## Architecture (who owns what) + For a specific account: -- **Gateway** owns the Baileys socket and inbox loop. -- **CLI / macOS app** talk to the gateway; no direct Baileys use. -- **Active listener** is required for outbound sends; otherwise send fails fast. +```bash +openclaw channels login --channel whatsapp --account work +``` -## Getting a phone number (two modes) + -WhatsApp requires a real mobile number for verification. VoIP and virtual numbers are usually blocked. There are two supported ways to run OpenClaw on WhatsApp: + -### Dedicated number (recommended) +```bash +openclaw gateway +``` -Use a **separate phone number** for OpenClaw. Best UX, clean routing, no self-chat quirks. Ideal setup: **spare/old Android phone + eSIM**. Leave it on Wi‑Fi and power, and link it via QR. + -**WhatsApp Business:** You can use WhatsApp Business on the same device with a different number. Great for keeping your personal WhatsApp separate — install WhatsApp Business and register the OpenClaw number there. + -**Sample config (dedicated number, single-user allowlist):** +```bash +openclaw pairing list whatsapp +openclaw pairing approve whatsapp +``` + + Pairing requests expire after 1 hour. Pending requests are capped at 3 per channel. + + + + + +OpenClaw recommends running WhatsApp on a separate number when possible. (The channel metadata and onboarding flow are optimized for that setup, but personal-number setups are also supported.) + + +## Deployment patterns + + + + This is the cleanest operational mode: + + - separate WhatsApp identity for OpenClaw + - clearer DM allowlists and routing boundaries + - lower chance of self-chat confusion + + Minimal policy pattern: + + ```json5 + { + channels: { + whatsapp: { + dmPolicy: "allowlist", + allowFrom: ["+15551234567"], + }, + }, + } + ``` + + + + + Onboarding supports personal-number mode and writes a self-chat-friendly baseline: + + - `dmPolicy: "allowlist"` + - `allowFrom` includes your personal number + - `selfChatMode: true` + + In runtime, self-chat protections key off the linked self number and `allowFrom`. + + + + + The messaging platform channel is WhatsApp Web-based (`Baileys`) in current OpenClaw channel architecture. + + There is no separate Twilio WhatsApp messaging channel in the built-in chat-channel registry. + + + + +## Runtime model + +- Gateway owns the WhatsApp socket and reconnect loop. +- Outbound sends require an active WhatsApp listener for the target account. +- Status and broadcast chats are ignored (`@status`, `@broadcast`). +- Direct chats use DM session rules (`session.dmScope`; default `main` collapses DMs to the agent main session). +- Group sessions are isolated (`agent::whatsapp:group:`). + +## Access control and activation + + + + `channels.whatsapp.dmPolicy` controls direct chat access: + + - `pairing` (default) + - `allowlist` + - `open` (requires `allowFrom` to include `"*"`) + - `disabled` + + `allowFrom` accepts E.164-style numbers (normalized internally). + + Runtime behavior details: + + - pairings are persisted in channel allow-store and merged with configured `allowFrom` + - if no allowlist is configured, the linked self number is allowed by default + - outbound `fromMe` DMs are never auto-paired + + + + + Group access has two layers: + + 1. **Group membership allowlist** (`channels.whatsapp.groups`) + - if `groups` is omitted, all groups are eligible + - if `groups` is present, it acts as a group allowlist (`"*"` allowed) + + 2. **Group sender policy** (`channels.whatsapp.groupPolicy` + `groupAllowFrom`) + - `open`: sender allowlist bypassed + - `allowlist`: sender must match `groupAllowFrom` (or `*`) + - `disabled`: block all group inbound + + Sender allowlist fallback: + + - if `groupAllowFrom` is unset, runtime falls back to `allowFrom` when available + + Note: if no `channels.whatsapp` block exists at all, runtime group-policy fallback is effectively `open`. + + + + + Group replies require mention by default. + + Mention detection includes: + + - explicit WhatsApp mentions of the bot identity + - configured mention regex patterns (`agents.list[].groupChat.mentionPatterns`, fallback `messages.groupChat.mentionPatterns`) + - implicit reply-to-bot detection (reply sender matches bot identity) + + Session-level activation command: + + - `/activation mention` + - `/activation always` + + `activation` updates session state (not global config). It is owner-gated. + + + + +## Personal-number and self-chat behavior + +When the linked self number is also present in `allowFrom`, WhatsApp self-chat safeguards activate: + +- skip read receipts for self-chat turns +- ignore mention-JID auto-trigger behavior that would otherwise ping yourself +- if `messages.responsePrefix` is unset, self-chat replies default to `[{identity.name}]` or `[openclaw]` + +## Message normalization and context + + + + Incoming WhatsApp messages are wrapped in the shared inbound envelope. + + If a quoted reply exists, context is appended in this form: + + ```text + [Replying to id:] + + [/Replying] + ``` + + Reply metadata fields are also populated when available (`ReplyToId`, `ReplyToBody`, `ReplyToSender`, sender JID/E.164). + + + + + Media-only inbound messages are normalized with placeholders such as: + + - `` + - `` + - `` + - `` + - `` + + Location and contact payloads are normalized into textual context before routing. + + + + + For groups, unprocessed messages can be buffered and injected as context when the bot is finally triggered. + + - default limit: `50` + - config: `channels.whatsapp.historyLimit` + - fallback: `messages.groupChat.historyLimit` + - `0` disables + + Injection markers: + + - `[Chat messages since your last reply - for context]` + - `[Current message - respond to this]` + + + + + Read receipts are enabled by default for accepted inbound WhatsApp messages. + + Disable globally: + + ```json5 + { + channels: { + whatsapp: { + sendReadReceipts: false, + }, + }, + } + ``` + + Per-account override: + + ```json5 + { + channels: { + whatsapp: { + accounts: { + work: { + sendReadReceipts: false, + }, + }, + }, + }, + } + ``` + + Self-chat turns skip read receipts even when globally enabled. + + + + +## Delivery, chunking, and media + + + + - default chunk limit: `channels.whatsapp.textChunkLimit = 4000` + - `channels.whatsapp.chunkMode = "length" | "newline"` + - `newline` mode prefers paragraph boundaries (blank lines), then falls back to length-safe chunking + + + + - supports image, video, audio (PTT voice-note), and document payloads + - `audio/ogg` is rewritten to `audio/ogg; codecs=opus` for voice-note compatibility + - animated GIF playback is supported via `gifPlayback: true` on video sends + - captions are applied to the first media item when sending multi-media reply payloads + - media source can be HTTP(S), `file://`, or local paths + + + + - inbound media save cap: `channels.whatsapp.mediaMaxMb` (default `50`) + - outbound media cap for auto-replies: `agents.defaults.mediaMaxMb` (default `5MB`) + - images are auto-optimized (resize/quality sweep) to fit limits + - on media send failure, first-item fallback sends text warning instead of dropping the response silently + + + +## Acknowledgment reactions + +WhatsApp supports immediate ack reactions on inbound receipt via `channels.whatsapp.ackReaction`. ```json5 { channels: { whatsapp: { - dmPolicy: "allowlist", - allowFrom: ["+15551234567"], - }, - }, -} -``` - -**Pairing mode (optional):** -If you want pairing instead of allowlist, set `channels.whatsapp.dmPolicy` to `pairing`. Unknown senders get a pairing code; approve with: -`openclaw pairing approve whatsapp ` - -### Personal number (fallback) - -Quick fallback: run OpenClaw on **your own number**. Message yourself (WhatsApp “Message yourself”) for testing so you don’t spam contacts. Expect to read verification codes on your main phone during setup and experiments. **Must enable self-chat mode.** -When the wizard asks for your personal WhatsApp number, enter the phone you will message from (the owner/sender), not the assistant number. - -**Sample config (personal number, self-chat):** - -```json -{ - "whatsapp": { - "selfChatMode": true, - "dmPolicy": "allowlist", - "allowFrom": ["+15551234567"] - } -} -``` - -Self-chat replies default to `[{identity.name}]` when set (otherwise `[openclaw]`) -if `messages.responsePrefix` is unset. Set it explicitly to customize or disable -the prefix (use `""` to remove it). - -### Number sourcing tips - -- **Local eSIM** from your country's mobile carrier (most reliable) - - Austria: [hot.at](https://www.hot.at) - - UK: [giffgaff](https://www.giffgaff.com) — free SIM, no contract -- **Prepaid SIM** — cheap, just needs to receive one SMS for verification - -**Avoid:** TextNow, Google Voice, most "free SMS" services — WhatsApp blocks these aggressively. - -**Tip:** The number only needs to receive one verification SMS. After that, WhatsApp Web sessions persist via `creds.json`. - -## Why Not Twilio? - -- Early OpenClaw builds supported Twilio’s WhatsApp Business integration. -- WhatsApp Business numbers are a poor fit for a personal assistant. -- Meta enforces a 24‑hour reply window; if you haven’t responded in the last 24 hours, the business number can’t initiate new messages. -- High-volume or “chatty” usage triggers aggressive blocking, because business accounts aren’t meant to send dozens of personal assistant messages. -- Result: unreliable delivery and frequent blocks, so support was removed. - -## Login + credentials - -- Login command: `openclaw channels login` (QR via Linked Devices). -- Multi-account login: `openclaw channels login --account ` (`` = `accountId`). -- Default account (when `--account` is omitted): `default` if present, otherwise the first configured account id (sorted). -- Credentials stored in `~/.openclaw/credentials/whatsapp//creds.json`. -- Backup copy at `creds.json.bak` (restored on corruption). -- Legacy compatibility: older installs stored Baileys files directly in `~/.openclaw/credentials/`. -- Logout: `openclaw channels logout` (or `--account `) deletes WhatsApp auth state (but keeps shared `oauth.json`). -- Logged-out socket => error instructs re-link. - -## Inbound flow (DM + group) - -- WhatsApp events come from `messages.upsert` (Baileys). -- Inbox listeners are detached on shutdown to avoid accumulating event handlers in tests/restarts. -- Status/broadcast chats are ignored. -- Direct chats use E.164; groups use group JID. -- **DM policy**: `channels.whatsapp.dmPolicy` controls direct chat access (default: `pairing`). - - Pairing: unknown senders get a pairing code (approve via `openclaw pairing approve whatsapp `; codes expire after 1 hour). - - Open: requires `channels.whatsapp.allowFrom` to include `"*"`. - - Your linked WhatsApp number is implicitly trusted, so self messages skip ⁠`channels.whatsapp.dmPolicy` and `channels.whatsapp.allowFrom` checks. - -### Personal-number mode (fallback) - -If you run OpenClaw on your **personal WhatsApp number**, enable `channels.whatsapp.selfChatMode` (see sample above). - -Behavior: - -- Outbound DMs never trigger pairing replies (prevents spamming contacts). -- Inbound unknown senders still follow `channels.whatsapp.dmPolicy`. -- Self-chat mode (allowFrom includes your number) avoids auto read receipts and ignores mention JIDs. -- Read receipts sent for non-self-chat DMs. - -## Read receipts - -By default, the gateway marks inbound WhatsApp messages as read (blue ticks) once they are accepted. - -Disable globally: - -```json5 -{ - channels: { whatsapp: { sendReadReceipts: false } }, -} -``` - -Disable per account: - -```json5 -{ - channels: { - whatsapp: { - accounts: { - personal: { sendReadReceipts: false }, + ackReaction: { + emoji: "👀", + direct: true, + group: "mentions", // always | mentions | never }, }, }, } ``` -Notes: +Behavior notes: -- Self-chat mode always skips read receipts. +- sent immediately after inbound is accepted (pre-reply) +- failures are logged but do not block normal reply delivery +- group mode `mentions` reacts on mention-triggered turns; group activation `always` acts as bypass for this check +- WhatsApp uses `channels.whatsapp.ackReaction` (legacy `messages.ackReaction` is not used here) -## WhatsApp FAQ: sending messages + pairing +## Multi-account and credentials -**Will OpenClaw message random contacts when I link WhatsApp?** -No. Default DM policy is **pairing**, so unknown senders only get a pairing code and their message is **not processed**. OpenClaw only replies to chats it receives, or to sends you explicitly trigger (agent/CLI). + + + - account ids come from `channels.whatsapp.accounts` + - default account selection: `default` if present, otherwise first configured account id (sorted) + - account ids are normalized internally for lookup + -**How does pairing work on WhatsApp?** -Pairing is a DM gate for unknown senders: + + - current auth path: `~/.openclaw/credentials/whatsapp//creds.json` + - backup file: `creds.json.bak` + - legacy default auth in `~/.openclaw/credentials/` is still recognized/migrated for default-account flows + -- First DM from a new sender returns a short code (message is not processed). -- Approve with: `openclaw pairing approve whatsapp ` (list with `openclaw pairing list whatsapp`). -- Codes expire after 1 hour; pending requests are capped at 3 per channel. + + `openclaw channels logout --channel whatsapp [--account ]` clears WhatsApp auth state for that account. -**Can multiple people use different OpenClaw instances on one WhatsApp number?** -Yes, by routing each sender to a different agent via `bindings` (peer `kind: "direct"`, sender E.164 like `+15551234567`). Replies still come from the **same WhatsApp account**, and direct chats collapse to each agent's main session, so use **one agent per person**. DM access control (`dmPolicy`/`allowFrom`) is global per WhatsApp account. See [Multi-Agent Routing](/concepts/multi-agent). + In legacy auth directories, `oauth.json` is preserved while Baileys auth files are removed. -**Why do you ask for my phone number in the wizard?** -The wizard uses it to set your **allowlist/owner** so your own DMs are permitted. It’s not used for auto-sending. If you run on your personal WhatsApp number, use that same number and enable `channels.whatsapp.selfChatMode`. + + -## Message normalization (what the model sees) +## Tools, actions, and config writes -- `Body` is the current message body with envelope. -- Quoted reply context is **always appended**: +- Agent tool support includes WhatsApp reaction action (`react`). +- Action gates: + - `channels.whatsapp.actions.reactions` + - `channels.whatsapp.actions.polls` +- Channel-initiated config writes are enabled by default (disable via `channels.whatsapp.configWrites=false`). - ``` - [Replying to +1555 id:ABC123] - > - [/Replying] - ``` +## Troubleshooting -- Reply metadata also set: - - `ReplyToId` = stanzaId - - `ReplyToBody` = quoted body or media placeholder - - `ReplyToSender` = E.164 when known -- Media-only inbound messages use placeholders: - - `` + + + Symptom: channel status reports not linked. -## Groups + Fix: -- Groups map to `agent::whatsapp:group:` sessions. -- Group policy: `channels.whatsapp.groupPolicy = open|disabled|allowlist` (default `allowlist`). -- Activation modes: - - `mention` (default): requires @mention or regex match. - - `always`: always triggers. -- `/activation mention|always` is owner-only and must be sent as a standalone message. -- Owner = `channels.whatsapp.allowFrom` (or self E.164 if unset). -- **History injection** (pending-only): - - Recent _unprocessed_ messages (default 50) inserted under: - `[Chat messages since your last reply - for context]` (messages already in the session are not re-injected) - - Current message under: - `[Current message - respond to this]` - - Sender suffix appended: `[from: Name (+E164)]` -- Group metadata cached 5 min (subject + participants). + ```bash + openclaw channels login --channel whatsapp + openclaw channels status + ``` -## Reply delivery (threading) + -- WhatsApp Web sends standard messages (no quoted reply threading in the current gateway). -- Reply tags are ignored on this channel. + + Symptom: linked account with repeated disconnects or reconnect attempts. -## Acknowledgment reactions (auto-react on receipt) + Fix: -WhatsApp can automatically send emoji reactions to incoming messages immediately upon receipt, before the bot generates a reply. This provides instant feedback to users that their message was received. + ```bash + openclaw doctor + openclaw logs --follow + ``` -**Configuration:** + If needed, re-link with `channels login`. -```json -{ - "whatsapp": { - "ackReaction": { - "emoji": "👀", - "direct": true, - "group": "mentions" - } - } -} -``` + -**Options:** + + Outbound sends fail fast when no active gateway listener exists for the target account. -- `emoji` (string): Emoji to use for acknowledgment (e.g., "👀", "✅", "📨"). Empty or omitted = feature disabled. -- `direct` (boolean, default: `true`): Send reactions in direct/DM chats. -- `group` (string, default: `"mentions"`): Group chat behavior: - - `"always"`: React to all group messages (even without @mention) - - `"mentions"`: React only when bot is @mentioned - - `"never"`: Never react in groups + Make sure gateway is running and the account is linked. -**Per-account override:** + -```json -{ - "whatsapp": { - "accounts": { - "work": { - "ackReaction": { - "emoji": "✅", - "direct": false, - "group": "always" - } - } - } - } -} -``` + + Check in this order: -**Behavior notes:** + - `groupPolicy` + - `groupAllowFrom` / `allowFrom` + - `groups` allowlist entries + - mention gating (`requireMention` + mention patterns) -- Reactions are sent **immediately** upon message receipt, before typing indicators or bot replies. -- In groups with `requireMention: false` (activation: always), `group: "mentions"` will react to all messages (not just @mentions). -- Fire-and-forget: reaction failures are logged but don't prevent the bot from replying. -- Participant JID is automatically included for group reactions. -- WhatsApp ignores `messages.ackReaction`; use `channels.whatsapp.ackReaction` instead. + -## Agent tool (reactions) + + WhatsApp gateway runtime should use Node. Bun is flagged as incompatible for stable WhatsApp/Telegram gateway operation. + + -- Tool: `whatsapp` with `react` action (`chatJid`, `messageId`, `emoji`, optional `remove`). -- Optional: `participant` (group sender), `fromMe` (reacting to your own message), `accountId` (multi-account). -- Reaction removal semantics: see [/tools/reactions](/tools/reactions). -- Tool gating: `channels.whatsapp.actions.reactions` (default: enabled). +## Configuration reference pointers -## Limits +Primary reference: -- Outbound text is chunked to `channels.whatsapp.textChunkLimit` (default 4000). -- Optional newline chunking: set `channels.whatsapp.chunkMode="newline"` to split on blank lines (paragraph boundaries) before length chunking. -- Inbound media saves are capped by `channels.whatsapp.mediaMaxMb` (default 50 MB). -- Outbound media items are capped by `agents.defaults.mediaMaxMb` (default 5 MB). +- [Configuration reference - WhatsApp](/gateway/configuration-reference#whatsapp) -## Outbound send (text + media) +High-signal WhatsApp fields: -- Uses active web listener; error if gateway not running. -- Text chunking: 4k max per message (configurable via `channels.whatsapp.textChunkLimit`, optional `channels.whatsapp.chunkMode`). -- Media: - - Image/video/audio/document supported. - - Audio sent as PTT; `audio/ogg` => `audio/ogg; codecs=opus`. - - Caption only on first media item. - - Media fetch supports HTTP(S) and local paths. - - Animated GIFs: WhatsApp expects MP4 with `gifPlayback: true` for inline looping. - - CLI: `openclaw message send --media --gif-playback` - - Gateway: `send` params include `gifPlayback: true` +- access: `dmPolicy`, `allowFrom`, `groupPolicy`, `groupAllowFrom`, `groups` +- delivery: `textChunkLimit`, `chunkMode`, `mediaMaxMb`, `sendReadReceipts`, `ackReaction` +- multi-account: `accounts..enabled`, `accounts..authDir`, account-level overrides +- operations: `configWrites`, `debounceMs`, `web.enabled`, `web.heartbeatSeconds`, `web.reconnect.*` +- session behavior: `session.dmScope`, `historyLimit`, `dmHistoryLimit`, `dms..historyLimit` -## Voice notes (PTT audio) +## Related -WhatsApp sends audio as **voice notes** (PTT bubble). - -- Best results: OGG/Opus. OpenClaw rewrites `audio/ogg` to `audio/ogg; codecs=opus`. -- `[[audio_as_voice]]` is ignored for WhatsApp (audio already ships as voice note). - -## Media limits + optimization - -- Default outbound cap: 5 MB (per media item). -- Override: `agents.defaults.mediaMaxMb`. -- Images are auto-optimized to JPEG under cap (resize + quality sweep). -- Oversize media => error; media reply falls back to text warning. - -## Heartbeats - -- **Gateway heartbeat** logs connection health (`web.heartbeatSeconds`, default 60s). -- **Agent heartbeat** can be configured per agent (`agents.list[].heartbeat`) or globally - via `agents.defaults.heartbeat` (fallback when no per-agent entries are set). - - Uses the configured heartbeat prompt (default: `Read HEARTBEAT.md if it exists (workspace context). Follow it strictly. Do not infer or repeat old tasks from prior chats. If nothing needs attention, reply HEARTBEAT_OK.`) + `HEARTBEAT_OK` skip behavior. - - Delivery defaults to the last used channel (or configured target). - -## Reconnect behavior - -- Backoff policy: `web.reconnect`: - - `initialMs`, `maxMs`, `factor`, `jitter`, `maxAttempts`. -- If maxAttempts reached, web monitoring stops (degraded). -- Logged-out => stop and require re-link. - -## Config quick map - -- `channels.whatsapp.dmPolicy` (DM policy: pairing/allowlist/open/disabled). -- `channels.whatsapp.selfChatMode` (same-phone setup; bot uses your personal WhatsApp number). -- `channels.whatsapp.allowFrom` (DM allowlist). WhatsApp uses E.164 phone numbers (no usernames). -- `channels.whatsapp.mediaMaxMb` (inbound media save cap). -- `channels.whatsapp.ackReaction` (auto-reaction on message receipt: `{emoji, direct, group}`). -- `channels.whatsapp.accounts..*` (per-account settings + optional `authDir`). -- `channels.whatsapp.accounts..mediaMaxMb` (per-account inbound media cap). -- `channels.whatsapp.accounts..ackReaction` (per-account ack reaction override). -- `channels.whatsapp.groupAllowFrom` (group sender allowlist). -- `channels.whatsapp.groupPolicy` (group policy). -- `channels.whatsapp.historyLimit` / `channels.whatsapp.accounts..historyLimit` (group history context; `0` disables). -- `channels.whatsapp.dmHistoryLimit` (DM history limit in user turns). Per-user overrides: `channels.whatsapp.dms[""].historyLimit`. -- `channels.whatsapp.groups` (group allowlist + mention gating defaults; use `"*"` to allow all) -- `channels.whatsapp.actions.reactions` (gate WhatsApp tool reactions). -- `agents.list[].groupChat.mentionPatterns` (or `messages.groupChat.mentionPatterns`) -- `messages.groupChat.historyLimit` -- `channels.whatsapp.messagePrefix` (inbound prefix; per-account: `channels.whatsapp.accounts..messagePrefix`; deprecated: `messages.messagePrefix`) -- `messages.responsePrefix` (outbound prefix) -- `agents.defaults.mediaMaxMb` -- `agents.defaults.heartbeat.every` -- `agents.defaults.heartbeat.model` (optional override) -- `agents.defaults.heartbeat.target` -- `agents.defaults.heartbeat.to` -- `agents.defaults.heartbeat.session` -- `agents.list[].heartbeat.*` (per-agent overrides) -- `session.*` (scope, idle, store, mainKey) -- `web.enabled` (disable channel startup when false) -- `web.heartbeatSeconds` -- `web.reconnect.*` - -## Logs + troubleshooting - -- Subsystems: `whatsapp/inbound`, `whatsapp/outbound`, `web-heartbeat`, `web-reconnect`. -- Log file: `/tmp/openclaw/openclaw-YYYY-MM-DD.log` (configurable). -- Troubleshooting guide: [Gateway troubleshooting](/gateway/troubleshooting). - -## Troubleshooting (quick) - -**Not linked / QR login required** - -- Symptom: `channels status` shows `linked: false` or warns “Not linked”. -- Fix: run `openclaw channels login` on the gateway host and scan the QR (WhatsApp → Settings → Linked Devices). - -**Linked but disconnected / reconnect loop** - -- Symptom: `channels status` shows `running, disconnected` or warns “Linked but disconnected”. -- Fix: `openclaw doctor` (or restart the gateway). If it persists, relink via `channels login` and inspect `openclaw logs --follow`. - -**Bun runtime** - -- Bun is **not recommended**. WhatsApp (Baileys) and Telegram are unreliable on Bun. - Run the gateway with **Node**. (See Getting Started runtime note.) +- [Pairing](/channels/pairing) +- [Channel routing](/channels/channel-routing) +- [Troubleshooting](/channels/troubleshooting)