{ "title": "WhatsApp (web channel)", "content": "Status: WhatsApp Web via Baileys only. Gateway owns the session(s).\n\n## Quick setup (beginner)\n\n1. Use a **separate phone number** if possible (recommended).\n2. Configure WhatsApp in `~/.openclaw/openclaw.json`.\n3. Run `openclaw channels login` to scan the QR code (Linked Devices).\n4. Start the gateway.\n\n* Multiple WhatsApp accounts (multi-account) in one Gateway process.\n* Deterministic routing: replies return to WhatsApp, no model routing.\n* Model sees enough context to understand quoted replies.\n\nBy default, WhatsApp is allowed to write config updates triggered by `/config set|unset` (requires `commands.config: true`).\n\n## Architecture (who owns what)\n\n* **Gateway** owns the Baileys socket and inbox loop.\n* **CLI / macOS app** talk to the gateway; no direct Baileys use.\n* **Active listener** is required for outbound sends; otherwise send fails fast.\n\n## Getting a phone number (two modes)\n\nWhatsApp requires a real mobile number for verification. VoIP and virtual numbers are usually blocked. There are two supported ways to run OpenClaw on WhatsApp:\n\n### Dedicated number (recommended)\n\nUse 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.\n\n**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.\n\n**Sample config (dedicated number, single-user allowlist):**\n\n**Pairing mode (optional):**\nIf you want pairing instead of allowlist, set `channels.whatsapp.dmPolicy` to `pairing`. Unknown senders get a pairing code; approve with:\n`openclaw pairing approve whatsapp `\n\n### Personal number (fallback)\n\nQuick 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.**\nWhen the wizard asks for your personal WhatsApp number, enter the phone you will message from (the owner/sender), not the assistant number.\n\n**Sample config (personal number, self-chat):**\n\nSelf-chat replies default to `[{identity.name}]` when set (otherwise `[openclaw]`)\nif `messages.responsePrefix` is unset. Set it explicitly to customize or disable\nthe prefix (use `\"\"` to remove it).\n\n### Number sourcing tips\n\n* **Local eSIM** from your country's mobile carrier (most reliable)\n * Austria: [hot.at](https://www.hot.at)\n * UK: [giffgaff](https://www.giffgaff.com) — free SIM, no contract\n* **Prepaid SIM** — cheap, just needs to receive one SMS for verification\n\n**Avoid:** TextNow, Google Voice, most \"free SMS\" services — WhatsApp blocks these aggressively.\n\n**Tip:** The number only needs to receive one verification SMS. After that, WhatsApp Web sessions persist via `creds.json`.\n\n* Early OpenClaw builds supported Twilio’s WhatsApp Business integration.\n* WhatsApp Business numbers are a poor fit for a personal assistant.\n* 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.\n* High-volume or “chatty” usage triggers aggressive blocking, because business accounts aren’t meant to send dozens of personal assistant messages.\n* Result: unreliable delivery and frequent blocks, so support was removed.\n\n## Login + credentials\n\n* Login command: `openclaw channels login` (QR via Linked Devices).\n* Multi-account login: `openclaw channels login --account ` (`` = `accountId`).\n* Default account (when `--account` is omitted): `default` if present, otherwise the first configured account id (sorted).\n* Credentials stored in `~/.openclaw/credentials/whatsapp//creds.json`.\n* Backup copy at `creds.json.bak` (restored on corruption).\n* Legacy compatibility: older installs stored Baileys files directly in `~/.openclaw/credentials/`.\n* Logout: `openclaw channels logout` (or `--account `) deletes WhatsApp auth state (but keeps shared `oauth.json`).\n* Logged-out socket => error instructs re-link.\n\n## Inbound flow (DM + group)\n\n* WhatsApp events come from `messages.upsert` (Baileys).\n* Inbox listeners are detached on shutdown to avoid accumulating event handlers in tests/restarts.\n* Status/broadcast chats are ignored.\n* Direct chats use E.164; groups use group JID.\n* **DM policy**: `channels.whatsapp.dmPolicy` controls direct chat access (default: `pairing`).\n * Pairing: unknown senders get a pairing code (approve via `openclaw pairing approve whatsapp `; codes expire after 1 hour).\n * Open: requires `channels.whatsapp.allowFrom` to include `\"*\"`.\n * Your linked WhatsApp number is implicitly trusted, so self messages skip ⁠`channels.whatsapp.dmPolicy` and `channels.whatsapp.allowFrom` checks.\n\n### Personal-number mode (fallback)\n\nIf you run OpenClaw on your **personal WhatsApp number**, enable `channels.whatsapp.selfChatMode` (see sample above).\n\n* Outbound DMs never trigger pairing replies (prevents spamming contacts).\n* Inbound unknown senders still follow `channels.whatsapp.dmPolicy`.\n* Self-chat mode (allowFrom includes your number) avoids auto read receipts and ignores mention JIDs.\n* Read receipts sent for non-self-chat DMs.\n\nBy default, the gateway marks inbound WhatsApp messages as read (blue ticks) once they are accepted.\n\n* Self-chat mode always skips read receipts.\n\n## WhatsApp FAQ: sending messages + pairing\n\n**Will OpenClaw message random contacts when I link WhatsApp?**\\\nNo. 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).\n\n**How does pairing work on WhatsApp?**\\\nPairing is a DM gate for unknown senders:\n\n* First DM from a new sender returns a short code (message is not processed).\n* Approve with: `openclaw pairing approve whatsapp ` (list with `openclaw pairing list whatsapp`).\n* Codes expire after 1 hour; pending requests are capped at 3 per channel.\n\n**Can multiple people use different OpenClaw instances on one WhatsApp number?**\\\nYes, 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).\n\n**Why do you ask for my phone number in the wizard?**\\\nThe 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`.\n\n## Message normalization (what the model sees)\n\n* `Body` is the current message body with envelope.\n* Quoted reply context is **always appended**:\n \n* Reply metadata also set:\n * `ReplyToId` = stanzaId\n * `ReplyToBody` = quoted body or media placeholder\n * `ReplyToSender` = E.164 when known\n* Media-only inbound messages use placeholders:\n * ``\n\n* Groups map to `agent::whatsapp:group:` sessions.\n* Group policy: `channels.whatsapp.groupPolicy = open|disabled|allowlist` (default `allowlist`).\n* Activation modes:\n * `mention` (default): requires @mention or regex match.\n * `always`: always triggers.\n* `/activation mention|always` is owner-only and must be sent as a standalone message.\n* Owner = `channels.whatsapp.allowFrom` (or self E.164 if unset).\n* **History injection** (pending-only):\n * Recent *unprocessed* messages (default 50) inserted under:\n `[Chat messages since your last reply - for context]` (messages already in the session are not re-injected)\n * Current message under:\n `[Current message - respond to this]`\n * Sender suffix appended: `[from: Name (+E164)]`\n* Group metadata cached 5 min (subject + participants).\n\n## Reply delivery (threading)\n\n* WhatsApp Web sends standard messages (no quoted reply threading in the current gateway).\n* Reply tags are ignored on this channel.\n\n## Acknowledgment reactions (auto-react on receipt)\n\nWhatsApp 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.\n\n* `emoji` (string): Emoji to use for acknowledgment (e.g., \"👀\", \"✅\", \"📨\"). Empty or omitted = feature disabled.\n* `direct` (boolean, default: `true`): Send reactions in direct/DM chats.\n* `group` (string, default: `\"mentions\"`): Group chat behavior:\n * `\"always\"`: React to all group messages (even without @mention)\n * `\"mentions\"`: React only when bot is @mentioned\n * `\"never\"`: Never react in groups\n\n**Per-account override:**\n\n* Reactions are sent **immediately** upon message receipt, before typing indicators or bot replies.\n* In groups with `requireMention: false` (activation: always), `group: \"mentions\"` will react to all messages (not just @mentions).\n* Fire-and-forget: reaction failures are logged but don't prevent the bot from replying.\n* Participant JID is automatically included for group reactions.\n* WhatsApp ignores `messages.ackReaction`; use `channels.whatsapp.ackReaction` instead.\n\n## Agent tool (reactions)\n\n* Tool: `whatsapp` with `react` action (`chatJid`, `messageId`, `emoji`, optional `remove`).\n* Optional: `participant` (group sender), `fromMe` (reacting to your own message), `accountId` (multi-account).\n* Reaction removal semantics: see [/tools/reactions](/tools/reactions).\n* Tool gating: `channels.whatsapp.actions.reactions` (default: enabled).\n\n* Outbound text is chunked to `channels.whatsapp.textChunkLimit` (default 4000).\n* Optional newline chunking: set `channels.whatsapp.chunkMode=\"newline\"` to split on blank lines (paragraph boundaries) before length chunking.\n* Inbound media saves are capped by `channels.whatsapp.mediaMaxMb` (default 50 MB).\n* Outbound media items are capped by `agents.defaults.mediaMaxMb` (default 5 MB).\n\n## Outbound send (text + media)\n\n* Uses active web listener; error if gateway not running.\n* Text chunking: 4k max per message (configurable via `channels.whatsapp.textChunkLimit`, optional `channels.whatsapp.chunkMode`).\n* Media:\n * Image/video/audio/document supported.\n * Audio sent as PTT; `audio/ogg` => `audio/ogg; codecs=opus`.\n * Caption only on first media item.\n * Media fetch supports HTTP(S) and local paths.\n * Animated GIFs: WhatsApp expects MP4 with `gifPlayback: true` for inline looping.\n * CLI: `openclaw message send --media --gif-playback`\n * Gateway: `send` params include `gifPlayback: true`\n\n## Voice notes (PTT audio)\n\nWhatsApp sends audio as **voice notes** (PTT bubble).\n\n* Best results: OGG/Opus. OpenClaw rewrites `audio/ogg` to `audio/ogg; codecs=opus`.\n* `[[audio_as_voice]]` is ignored for WhatsApp (audio already ships as voice note).\n\n## Media limits + optimization\n\n* Default outbound cap: 5 MB (per media item).\n* Override: `agents.defaults.mediaMaxMb`.\n* Images are auto-optimized to JPEG under cap (resize + quality sweep).\n* Oversize media => error; media reply falls back to text warning.\n\n* **Gateway heartbeat** logs connection health (`web.heartbeatSeconds`, default 60s).\n* **Agent heartbeat** can be configured per agent (`agents.list[].heartbeat`) or globally\n via `agents.defaults.heartbeat` (fallback when no per-agent entries are set).\n * 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.\n * Delivery defaults to the last used channel (or configured target).\n\n## Reconnect behavior\n\n* Backoff policy: `web.reconnect`:\n * `initialMs`, `maxMs`, `factor`, `jitter`, `maxAttempts`.\n* If maxAttempts reached, web monitoring stops (degraded).\n* Logged-out => stop and require re-link.\n\n* `channels.whatsapp.dmPolicy` (DM policy: pairing/allowlist/open/disabled).\n* `channels.whatsapp.selfChatMode` (same-phone setup; bot uses your personal WhatsApp number).\n* `channels.whatsapp.allowFrom` (DM allowlist). WhatsApp uses E.164 phone numbers (no usernames).\n* `channels.whatsapp.mediaMaxMb` (inbound media save cap).\n* `channels.whatsapp.ackReaction` (auto-reaction on message receipt: `{emoji, direct, group}`).\n* `channels.whatsapp.accounts..*` (per-account settings + optional `authDir`).\n* `channels.whatsapp.accounts..mediaMaxMb` (per-account inbound media cap).\n* `channels.whatsapp.accounts..ackReaction` (per-account ack reaction override).\n* `channels.whatsapp.groupAllowFrom` (group sender allowlist).\n* `channels.whatsapp.groupPolicy` (group policy).\n* `channels.whatsapp.historyLimit` / `channels.whatsapp.accounts..historyLimit` (group history context; `0` disables).\n* `channels.whatsapp.dmHistoryLimit` (DM history limit in user turns). Per-user overrides: `channels.whatsapp.dms[\"\"].historyLimit`.\n* `channels.whatsapp.groups` (group allowlist + mention gating defaults; use `\"*\"` to allow all)\n* `channels.whatsapp.actions.reactions` (gate WhatsApp tool reactions).\n* `agents.list[].groupChat.mentionPatterns` (or `messages.groupChat.mentionPatterns`)\n* `messages.groupChat.historyLimit`\n* `channels.whatsapp.messagePrefix` (inbound prefix; per-account: `channels.whatsapp.accounts..messagePrefix`; deprecated: `messages.messagePrefix`)\n* `messages.responsePrefix` (outbound prefix)\n* `agents.defaults.mediaMaxMb`\n* `agents.defaults.heartbeat.every`\n* `agents.defaults.heartbeat.model` (optional override)\n* `agents.defaults.heartbeat.target`\n* `agents.defaults.heartbeat.to`\n* `agents.defaults.heartbeat.session`\n* `agents.list[].heartbeat.*` (per-agent overrides)\n* `session.*` (scope, idle, store, mainKey)\n* `web.enabled` (disable channel startup when false)\n* `web.heartbeatSeconds`\n* `web.reconnect.*`\n\n## Logs + troubleshooting\n\n* Subsystems: `whatsapp/inbound`, `whatsapp/outbound`, `web-heartbeat`, `web-reconnect`.\n* Log file: `/tmp/openclaw/openclaw-YYYY-MM-DD.log` (configurable).\n* Troubleshooting guide: [Gateway troubleshooting](/gateway/troubleshooting).\n\n## Troubleshooting (quick)\n\n**Not linked / QR login required**\n\n* Symptom: `channels status` shows `linked: false` or warns “Not linked”.\n* Fix: run `openclaw channels login` on the gateway host and scan the QR (WhatsApp → Settings → Linked Devices).\n\n**Linked but disconnected / reconnect loop**\n\n* Symptom: `channels status` shows `running, disconnected` or warns “Linked but disconnected”.\n* Fix: `openclaw doctor` (or restart the gateway). If it persists, relink via `channels login` and inspect `openclaw logs --follow`.\n\n* Bun is **not recommended**. WhatsApp (Baileys) and Telegram are unreliable on Bun.\n Run the gateway with **Node**. (See Getting Started runtime note.)", "code_samples": [ { "code": "## Goals\n\n* Multiple WhatsApp accounts (multi-account) in one Gateway process.\n* Deterministic routing: replies return to WhatsApp, no model routing.\n* Model sees enough context to understand quoted replies.\n\n## Config writes\n\nBy default, WhatsApp is allowed to write config updates triggered by `/config set|unset` (requires `commands.config: true`).\n\nDisable with:", "language": "unknown" }, { "code": "## Architecture (who owns what)\n\n* **Gateway** owns the Baileys socket and inbox loop.\n* **CLI / macOS app** talk to the gateway; no direct Baileys use.\n* **Active listener** is required for outbound sends; otherwise send fails fast.\n\n## Getting a phone number (two modes)\n\nWhatsApp requires a real mobile number for verification. VoIP and virtual numbers are usually blocked. There are two supported ways to run OpenClaw on WhatsApp:\n\n### Dedicated number (recommended)\n\nUse 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.\n\n**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.\n\n**Sample config (dedicated number, single-user allowlist):**", "language": "unknown" }, { "code": "**Pairing mode (optional):**\nIf you want pairing instead of allowlist, set `channels.whatsapp.dmPolicy` to `pairing`. Unknown senders get a pairing code; approve with:\n`openclaw pairing approve whatsapp `\n\n### Personal number (fallback)\n\nQuick 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.**\nWhen the wizard asks for your personal WhatsApp number, enter the phone you will message from (the owner/sender), not the assistant number.\n\n**Sample config (personal number, self-chat):**", "language": "unknown" }, { "code": "Self-chat replies default to `[{identity.name}]` when set (otherwise `[openclaw]`)\nif `messages.responsePrefix` is unset. Set it explicitly to customize or disable\nthe prefix (use `\"\"` to remove it).\n\n### Number sourcing tips\n\n* **Local eSIM** from your country's mobile carrier (most reliable)\n * Austria: [hot.at](https://www.hot.at)\n * UK: [giffgaff](https://www.giffgaff.com) — free SIM, no contract\n* **Prepaid SIM** — cheap, just needs to receive one SMS for verification\n\n**Avoid:** TextNow, Google Voice, most \"free SMS\" services — WhatsApp blocks these aggressively.\n\n**Tip:** The number only needs to receive one verification SMS. After that, WhatsApp Web sessions persist via `creds.json`.\n\n## Why Not Twilio?\n\n* Early OpenClaw builds supported Twilio’s WhatsApp Business integration.\n* WhatsApp Business numbers are a poor fit for a personal assistant.\n* 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.\n* High-volume or “chatty” usage triggers aggressive blocking, because business accounts aren’t meant to send dozens of personal assistant messages.\n* Result: unreliable delivery and frequent blocks, so support was removed.\n\n## Login + credentials\n\n* Login command: `openclaw channels login` (QR via Linked Devices).\n* Multi-account login: `openclaw channels login --account ` (`` = `accountId`).\n* Default account (when `--account` is omitted): `default` if present, otherwise the first configured account id (sorted).\n* Credentials stored in `~/.openclaw/credentials/whatsapp//creds.json`.\n* Backup copy at `creds.json.bak` (restored on corruption).\n* Legacy compatibility: older installs stored Baileys files directly in `~/.openclaw/credentials/`.\n* Logout: `openclaw channels logout` (or `--account `) deletes WhatsApp auth state (but keeps shared `oauth.json`).\n* Logged-out socket => error instructs re-link.\n\n## Inbound flow (DM + group)\n\n* WhatsApp events come from `messages.upsert` (Baileys).\n* Inbox listeners are detached on shutdown to avoid accumulating event handlers in tests/restarts.\n* Status/broadcast chats are ignored.\n* Direct chats use E.164; groups use group JID.\n* **DM policy**: `channels.whatsapp.dmPolicy` controls direct chat access (default: `pairing`).\n * Pairing: unknown senders get a pairing code (approve via `openclaw pairing approve whatsapp `; codes expire after 1 hour).\n * Open: requires `channels.whatsapp.allowFrom` to include `\"*\"`.\n * Your linked WhatsApp number is implicitly trusted, so self messages skip ⁠`channels.whatsapp.dmPolicy` and `channels.whatsapp.allowFrom` checks.\n\n### Personal-number mode (fallback)\n\nIf you run OpenClaw on your **personal WhatsApp number**, enable `channels.whatsapp.selfChatMode` (see sample above).\n\nBehavior:\n\n* Outbound DMs never trigger pairing replies (prevents spamming contacts).\n* Inbound unknown senders still follow `channels.whatsapp.dmPolicy`.\n* Self-chat mode (allowFrom includes your number) avoids auto read receipts and ignores mention JIDs.\n* Read receipts sent for non-self-chat DMs.\n\n## Read receipts\n\nBy default, the gateway marks inbound WhatsApp messages as read (blue ticks) once they are accepted.\n\nDisable globally:", "language": "unknown" }, { "code": "Disable per account:", "language": "unknown" }, { "code": "Notes:\n\n* Self-chat mode always skips read receipts.\n\n## WhatsApp FAQ: sending messages + pairing\n\n**Will OpenClaw message random contacts when I link WhatsApp?**\\\nNo. 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).\n\n**How does pairing work on WhatsApp?**\\\nPairing is a DM gate for unknown senders:\n\n* First DM from a new sender returns a short code (message is not processed).\n* Approve with: `openclaw pairing approve whatsapp ` (list with `openclaw pairing list whatsapp`).\n* Codes expire after 1 hour; pending requests are capped at 3 per channel.\n\n**Can multiple people use different OpenClaw instances on one WhatsApp number?**\\\nYes, 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).\n\n**Why do you ask for my phone number in the wizard?**\\\nThe 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`.\n\n## Message normalization (what the model sees)\n\n* `Body` is the current message body with envelope.\n* Quoted reply context is **always appended**:", "language": "unknown" }, { "code": "* Reply metadata also set:\n * `ReplyToId` = stanzaId\n * `ReplyToBody` = quoted body or media placeholder\n * `ReplyToSender` = E.164 when known\n* Media-only inbound messages use placeholders:\n * ``\n\n## Groups\n\n* Groups map to `agent::whatsapp:group:` sessions.\n* Group policy: `channels.whatsapp.groupPolicy = open|disabled|allowlist` (default `allowlist`).\n* Activation modes:\n * `mention` (default): requires @mention or regex match.\n * `always`: always triggers.\n* `/activation mention|always` is owner-only and must be sent as a standalone message.\n* Owner = `channels.whatsapp.allowFrom` (or self E.164 if unset).\n* **History injection** (pending-only):\n * Recent *unprocessed* messages (default 50) inserted under:\n `[Chat messages since your last reply - for context]` (messages already in the session are not re-injected)\n * Current message under:\n `[Current message - respond to this]`\n * Sender suffix appended: `[from: Name (+E164)]`\n* Group metadata cached 5 min (subject + participants).\n\n## Reply delivery (threading)\n\n* WhatsApp Web sends standard messages (no quoted reply threading in the current gateway).\n* Reply tags are ignored on this channel.\n\n## Acknowledgment reactions (auto-react on receipt)\n\nWhatsApp 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.\n\n**Configuration:**", "language": "unknown" }, { "code": "**Options:**\n\n* `emoji` (string): Emoji to use for acknowledgment (e.g., \"👀\", \"✅\", \"📨\"). Empty or omitted = feature disabled.\n* `direct` (boolean, default: `true`): Send reactions in direct/DM chats.\n* `group` (string, default: `\"mentions\"`): Group chat behavior:\n * `\"always\"`: React to all group messages (even without @mention)\n * `\"mentions\"`: React only when bot is @mentioned\n * `\"never\"`: Never react in groups\n\n**Per-account override:**", "language": "unknown" } ], "headings": [ { "level": "h2", "text": "Quick setup (beginner)", "id": "quick-setup-(beginner)" }, { "level": "h2", "text": "Goals", "id": "goals" }, { "level": "h2", "text": "Config writes", "id": "config-writes" }, { "level": "h2", "text": "Architecture (who owns what)", "id": "architecture-(who-owns-what)" }, { "level": "h2", "text": "Getting a phone number (two modes)", "id": "getting-a-phone-number-(two-modes)" }, { "level": "h3", "text": "Dedicated number (recommended)", "id": "dedicated-number-(recommended)" }, { "level": "h3", "text": "Personal number (fallback)", "id": "personal-number-(fallback)" }, { "level": "h3", "text": "Number sourcing tips", "id": "number-sourcing-tips" }, { "level": "h2", "text": "Why Not Twilio?", "id": "why-not-twilio?" }, { "level": "h2", "text": "Login + credentials", "id": "login-+-credentials" }, { "level": "h2", "text": "Inbound flow (DM + group)", "id": "inbound-flow-(dm-+-group)" }, { "level": "h3", "text": "Personal-number mode (fallback)", "id": "personal-number-mode-(fallback)" }, { "level": "h2", "text": "Read receipts", "id": "read-receipts" }, { "level": "h2", "text": "WhatsApp FAQ: sending messages + pairing", "id": "whatsapp-faq:-sending-messages-+-pairing" }, { "level": "h2", "text": "Message normalization (what the model sees)", "id": "message-normalization-(what-the-model-sees)" }, { "level": "h2", "text": "Groups", "id": "groups" }, { "level": "h2", "text": "Reply delivery (threading)", "id": "reply-delivery-(threading)" }, { "level": "h2", "text": "Acknowledgment reactions (auto-react on receipt)", "id": "acknowledgment-reactions-(auto-react-on-receipt)" }, { "level": "h2", "text": "Agent tool (reactions)", "id": "agent-tool-(reactions)" }, { "level": "h2", "text": "Limits", "id": "limits" }, { "level": "h2", "text": "Outbound send (text + media)", "id": "outbound-send-(text-+-media)" }, { "level": "h2", "text": "Voice notes (PTT audio)", "id": "voice-notes-(ptt-audio)" }, { "level": "h2", "text": "Media limits + optimization", "id": "media-limits-+-optimization" }, { "level": "h2", "text": "Heartbeats", "id": "heartbeats" }, { "level": "h2", "text": "Reconnect behavior", "id": "reconnect-behavior" }, { "level": "h2", "text": "Config quick map", "id": "config-quick-map" }, { "level": "h2", "text": "Logs + troubleshooting", "id": "logs-+-troubleshooting" }, { "level": "h2", "text": "Troubleshooting (quick)", "id": "troubleshooting-(quick)" } ], "url": "llms-txt#whatsapp-(web-channel)", "links": [] }