--- summary: "WhatsApp (web provider) integration: login, inbox, replies, media, and ops" read_when: - Working on WhatsApp/web provider behavior or inbox routing --- # WhatsApp (web provider) Status: WhatsApp Web via Baileys only. Gateway owns the session(s). ## Quick setup (beginner) 1) Use a **separate phone number** if possible (recommended). 2) Configure WhatsApp in `~/.clawdbot/clawdbot.json`. 3) Run `clawdbot providers login` to scan the QR code (Linked Devices). 4) Start the gateway. Minimal config: ```json5 { whatsapp: { dmPolicy: "allowlist", allowFrom: ["+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. ## Architecture (who owns what) - **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. ## 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 Clawdbot on WhatsApp: ### Dedicated number (recommended) Use a **separate phone number** for Clawdbot. 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 Clawdbot number there. **Sample config (dedicated number, single-user allowlist):** ```json { "whatsapp": { "dmPolicy": "allowlist", "allowFrom": ["+15551234567"] } } ``` **Pairing mode (optional):** If you want pairing instead of allowlist, set `whatsapp.dmPolicy` to `pairing`. Unknown senders get a pairing code; approve with: `clawdbot pairing approve whatsapp ` ### Personal number (fallback) Quick fallback: run Clawdbot 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"] }, "messages": { "responsePrefix": "[clawdbot]" } } ``` Tip: set `messages.responsePrefix` explicitly if you want a consistent bot prefix on outbound replies. ### 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 Clawdbot 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: `clawdbot providers login` (QR via Linked Devices). - Multi-account login: `clawdbot providers login --account ` (`` = `accountId`). - Default account (when `--account` is omitted): `default` if present, otherwise the first configured account id (sorted). - Credentials stored in `~/.clawdbot/credentials/whatsapp//creds.json`. - Backup copy at `creds.json.bak` (restored on corruption). - Legacy compatibility: older installs stored Baileys files directly in `~/.clawdbot/credentials/`. - Logout: `clawdbot providers 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**: `whatsapp.dmPolicy` controls direct chat access (default: `pairing`). - Pairing: unknown senders get a pairing code (approve via `clawdbot pairing approve whatsapp `; codes expire after 1 hour). - Open: requires `whatsapp.allowFrom` to include `"*"`. - Self messages are always allowed; “self-chat mode” still requires `whatsapp.allowFrom` to include your own number. ### Personal-number mode (fallback) If you run Clawdbot on your **personal WhatsApp number**, enable `whatsapp.selfChatMode` (see sample above). Behavior: - Outbound DMs never trigger pairing replies (prevents spamming contacts). - Inbound unknown senders still follow `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. ## WhatsApp FAQ: sending messages + pairing **Will Clawdbot 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**. Clawdbot only replies to chats it receives, or to sends you explicitly trigger (agent/CLI). **How does pairing work on WhatsApp?** Pairing is a DM gate for unknown senders: - First DM from a new sender returns a short code (message is not processed). - Approve with: `clawdbot pairing approve whatsapp ` (list with `clawdbot pairing list whatsapp`). - Codes expire after 1 hour; pending requests are capped at 3 per provider. **Can multiple people use different Clawdbots on one WhatsApp number?** Yes, by routing each sender to a different agent via `bindings` (peer `kind: "dm"`, 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). **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 `whatsapp.selfChatMode`. ## Message normalization (what the model sees) - `Body` is the current message body with envelope. - Quoted reply context is **always appended**: ``` [Replying to +1555 id:ABC123] > [/Replying] ``` - Reply metadata also set: - `ReplyToId` = stanzaId - `ReplyToBody` = quoted body or media placeholder - `ReplyToSender` = E.164 when known - Media-only inbound messages use placeholders: - `` ## Groups - Groups map to `agent::whatsapp:group:` sessions. - Group policy: `whatsapp.groupPolicy = open|disabled|allowlist` (default `open`). - 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 = `whatsapp.allowFrom` (or self E.164 if unset). - **History injection**: - Recent messages (default 50) inserted under: `[Chat messages since your last reply - for context]` - Current message under: `[Current message - respond to this]` - Sender suffix appended: `[from: Name (+E164)]` - Group metadata cached 5 min (subject + participants). ## Reply delivery (threading) - WhatsApp Web sends standard messages (no quoted reply threading in the current gateway). - Reply tags are ignored on this provider. ## Acknowledgment reactions (auto-react on receipt) 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. **Configuration:** ```json { "whatsapp": { "ackReaction": { "emoji": "👀", "direct": true, "group": "mentions" } } } ``` **Options:** - `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 **Per-account override:** ```json { "whatsapp": { "accounts": { "work": { "ackReaction": { "emoji": "✅", "direct": false, "group": "always" } } } } } ``` **Behavior notes:** - 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 `whatsapp.ackReaction` instead. ## Agent tool (reactions) - 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: `whatsapp.actions.reactions` (default: enabled). ## Limits - Outbound text is chunked to `whatsapp.textChunkLimit` (default 4000). - Inbound media saves are capped by `whatsapp.mediaMaxMb` (default 50 MB). - Outbound media items are capped by `agents.defaults.mediaMaxMb` (default 5 MB). ## Outbound send (text + media) - Uses active web listener; error if gateway not running. - Text chunking: 4k max per message (configurable via `whatsapp.textChunkLimit`). - 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: `clawdbot message send --media --gif-playback` - Gateway: `send` params include `gifPlayback: true` ## 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** is global (`agents.defaults.heartbeat.*`) and runs in the main session. - Uses the configured heartbeat prompt (default: `Read HEARTBEAT.md if exists. Consider outstanding tasks. Checkup sometimes on your human during (user local) day time.`) + `HEARTBEAT_OK` skip behavior. - Delivery defaults to the last used provider (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 - `whatsapp.dmPolicy` (DM policy: pairing/allowlist/open/disabled). - `whatsapp.selfChatMode` (same-phone setup; bot uses your personal WhatsApp number). - `whatsapp.allowFrom` (DM allowlist). - `whatsapp.mediaMaxMb` (inbound media save cap). - `whatsapp.ackReaction` (auto-reaction on message receipt: `{emoji, direct, group}`). - `whatsapp.accounts..*` (per-account settings + optional `authDir`). - `whatsapp.accounts..mediaMaxMb` (per-account inbound media cap). - `whatsapp.accounts..ackReaction` (per-account ack reaction override). - `whatsapp.groupAllowFrom` (group sender allowlist). - `whatsapp.groupPolicy` (group policy). - `whatsapp.historyLimit` / `whatsapp.accounts..historyLimit` (group history context; `0` disables). - `whatsapp.groups` (group allowlist + mention gating defaults; use `"*"` to allow all) - `whatsapp.actions.reactions` (gate WhatsApp tool reactions). - `agents.list[].groupChat.mentionPatterns` (or `messages.groupChat.mentionPatterns`) - `messages.groupChat.historyLimit` - `whatsapp.messagePrefix` (inbound prefix; per-account: `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` - `session.*` (scope, idle, store, mainKey) - `web.enabled` (disable provider startup when false) - `web.heartbeatSeconds` - `web.reconnect.*` ## Logs + troubleshooting - Subsystems: `whatsapp/inbound`, `whatsapp/outbound`, `web-heartbeat`, `web-reconnect`. - Log file: `/tmp/clawdbot/clawdbot-YYYY-MM-DD.log` (configurable). - Troubleshooting guide: [Gateway troubleshooting](/gateway/troubleshooting). ## Troubleshooting (quick) **Not linked / QR login required** - Symptom: `providers status` shows `linked: false` or warns “Not linked”. - Fix: run `clawdbot providers login` on the gateway host and scan the QR (WhatsApp → Settings → Linked Devices). **Linked but disconnected / reconnect loop** - Symptom: `providers status` shows `running, disconnected` or warns “Linked but disconnected”. - Fix: `clawdbot doctor` (or restart the gateway). If it persists, relink via `providers login` and inspect `clawdbot logs --follow`. **Bun runtime** - WhatsApp uses Baileys; run the gateway with **Node** for WhatsApp. (See Getting Started runtime note.)