Skip to main content

WhatsApp (Web channel)

Status: production-ready via WhatsApp Web (Baileys). Gateway owns linked session(s).

Quick setup

1

Configure WhatsApp access policy

{
  channels: {
    whatsapp: {
      dmPolicy: "pairing",
      allowFrom: ["+15551234567"],
      groupPolicy: "allowlist",
      groupAllowFrom: ["+15551234567"],
    },
  },
}
2

Link WhatsApp (QR)

openclaw channels login --channel whatsapp
For a specific account:
openclaw channels login --channel whatsapp --account work
3

Start the gateway

openclaw gateway
4

Approve first pairing request (if using pairing mode)

openclaw pairing list whatsapp
openclaw pairing approve whatsapp <CODE>
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

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:<agentId>:whatsapp:group:<jid>).

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).Multi-account override: channels.whatsapp.accounts.<id>.dmPolicy (and allowFrom) take precedence over channel-level defaults for that account.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

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:
[Replying to <sender> id:<stanzaId>]
<quoted body or media placeholder>
[/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:
  • <media:image>
  • <media:video>
  • <media:audio>
  • <media:document>
  • <media:sticker>
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:
{
  channels: {
    whatsapp: {
      sendReadReceipts: false,
    },
  },
}
Per-account override:
{
  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.
{
  channels: {
    whatsapp: {
      ackReaction: {
        emoji: "👀",
        direct: true,
        group: "mentions", // always | mentions | never
      },
    },
  },
}
Behavior notes:
  • 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)

Multi-account and credentials

  • 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
  • current auth path: ~/.openclaw/credentials/whatsapp/<accountId>/creds.json
  • backup file: creds.json.bak
  • legacy default auth in ~/.openclaw/credentials/ is still recognized/migrated for default-account flows
openclaw channels logout --channel whatsapp [--account <id>] clears WhatsApp auth state for that account.In legacy auth directories, oauth.json is preserved while Baileys auth files are removed.

Tools, actions, and config writes

  • 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).

Troubleshooting

Symptom: channel status reports not linked.Fix:
openclaw channels login --channel whatsapp
openclaw channels status
Symptom: linked account with repeated disconnects or reconnect attempts.Fix:
openclaw doctor
openclaw logs --follow
If needed, re-link with channels login.
Outbound sends fail fast when no active gateway listener exists for the target account.Make sure gateway is running and the account is linked.
Check in this order:
  • groupPolicy
  • groupAllowFrom / allowFrom
  • groups allowlist entries
  • mention gating (requireMention + mention patterns)
WhatsApp gateway runtime should use Node. Bun is flagged as incompatible for stable WhatsApp/Telegram gateway operation.

Configuration reference pointers

Primary reference: High-signal WhatsApp fields:
  • access: dmPolicy, allowFrom, groupPolicy, groupAllowFrom, groups
  • delivery: textChunkLimit, chunkMode, mediaMaxMb, sendReadReceipts, ackReaction
  • multi-account: accounts.<id>.enabled, accounts.<id>.authDir, account-level overrides
  • operations: configWrites, debounceMs, web.enabled, web.heartbeatSeconds, web.reconnect.*
  • session behavior: session.dmScope, historyLimit, dmHistoryLimit, dms.<id>.historyLimit