Skip to main content

WhatsApp

Status: production-ready via WhatsApp Web (Baileys). The whatsapp bundled extension owns platform dependencies, login, session inspection, outbound delivery, polls, and reactions. @whiskeysockets/baileys is resolved lazily at channel-activation time — the gateway boots and the extension registers its config/status/onboarding surface even when the runtime dependency is not installed (see Installation below).
If you see deps missing for the whatsapp plugin in wednesdayai plugins list, reinstall the managed copy:
The install copies WhatsApp into ~/.openclaw/extensions/whatsapp and installs its runtime dependencies there. Do not run npm install inside the global WednesdayAI package directory. The gateway will emit a PLUGIN_DEPS_MISSING warning at boot until the managed copy has its dependencies.

Pairing

Default DM policy is pairing for unknown senders.

Channel troubleshooting

Cross-channel diagnostics and repair playbooks.

Gateway configuration

Full channel config patterns and examples.

Quick setup

1

Configure WhatsApp access policy

2

Link WhatsApp (QR)

For a specific account:
3

Start the gateway

4

Approve first pairing request (if using pairing mode)

Pairing requests expire after 1 hour. Pending requests are capped at 3 per channel.
WednesdayAI 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.channels.whatsapp.selfChatMode enables same-phone setup where the bot uses your personal WhatsApp number. When enabled, self-chat safeguards activate (skip read receipts, ignore mention-JID auto-trigger, default response prefix).
The messaging platform transport is WhatsApp Web-based (Baileys) in current WednesdayAI channel architecture.There is no separate Twilio WhatsApp messaging channel in the built-in chat-channel registry.

Installation

Install the WhatsApp plugin with one command:
WhatsApp is the first dependency-bearing first-party npm plugin. The catalog resolves whatsapp to @wednesdayai/whatsapp, installs it into ~/.openclaw/extensions/whatsapp, and installs Baileys plus QR-code runtime dependencies beside that managed copy. The plugin pins @whiskeysockets/baileys 7.0.0-rc14. That release depends on registry libsignal (not a git-protocol spec) and on whatsapp-rust-bridge 0.5.4 (Rust/WebAssembly utilities published as a registry package). After install, enable it and restart the gateway. WhatsApp configuration (phone number, account settings) can be set before or after installation — it validates regardless of install state. The global bundled WhatsApp copy is still used for catalog metadata and onboarding labels. Runtime loads should use the managed copy under ~/.openclaw/extensions/whatsapp because WhatsApp depends on Baileys and QR-code packages that are installed beside that managed copy.

Runtime model

  • The whatsapp extension owns account config resolution, auth/session inspection, QR login helpers, outbound send/poll/reaction behavior, and extension runtime dependencies.
  • Gateway still owns the active listener and monitor loop while inbound monitoring is being extracted.
  • During this stage, stateful auth operations in the extension proxy through the host runtime so QR login, monitor sockets, queued credential saves, and logout clearing share one auth-state owner.
  • Outbound sends require an active WhatsApp listener for the target account.
  • Direct QR relink refuses to open a second socket while the gateway listener owns the account. Use the gateway login flow, which stops the channel before starting QR relink.
  • Logout stops gateway listeners that share the same canonical auth directory before clearing credentials, so relink checks do not see stale active-listener ownership.
  • WhatsApp socket startup also takes a non-reentrant auth-directory lock, so a separate CLI, gateway process, or second in-process startup cannot open another Baileys socket on the same account credentials.
  • 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:
Reply metadata fields are also populated when available (ReplyToId, ReplyToBody, ReplyToSender, sender JID/E.164).channels.whatsapp.messagePrefix adds a prefix to inbound messages before agent processing (e.g. for context tagging).
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:
Per-account override:
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/block boundaries and packs as many complete paragraphs as fit into each message up to textChunkLimit, falling back to length-based splitting only when a single block exceeds the limit. Fenced code blocks and tables are kept intact.
  • channels.whatsapp.chunkNewlinePacking (default true): set to false to restore one message per paragraph (the pre-packing behaviour).
channels.whatsapp.debounceMs batches rapid consecutive messages from the same sender before agent processing. Default behavior depends on configuration; set to 0 to disable.
  • 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.
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
  • during the current extraction stage, the extension auth-store uses a host-runtime proxy so QR login, monitor sockets, credential save queues, and logout clearing share one process-local state map
  • active WhatsApp sockets create .socket-owner.lock next to the credentials to prevent cross-process gateway/CLI collisions; stale locks from dead processes are removed automatically by PID checks
  • compatibility SDK auth exports share the same process-scoped queues/clear markers; new extension code should prefer the channel runtime bridge while the monitor remains host-owned
openclaw channels logout --channel whatsapp [--account <id>] clears WhatsApp auth state for that account.Logout closes active QR sockets and stops gateway listeners that share the selected account’s canonical auth directory, refuses to clear credentials if another socket owner holds the lock, drains queued credential writes, then deletes Baileys auth JSON files from the configured auth directory. Symlinked auth directories are refused rather than followed; symlinked Baileys auth fragments inside a real auth directory are unlinked without touching their targets. In legacy shared auth directories, unrelated JSON state is preserved and only known Baileys fragments are removed; oauth.json is preserved.

Disabling without removing config

Set channels.whatsapp.enabled: false to disable the WhatsApp channel while preserving the rest of your config:
Restart the gateway for the change to take effect.

Tools, actions, and config writes

  • Agent tool support includes WhatsApp reaction action (react).
  • Outbound text, media, polls, and reactions are implemented in the WhatsApp extension.
  • Action gates:
    • channels.whatsapp.actions.reactions
    • channels.whatsapp.actions.sendMessage
    • 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:
Symptom: linked account with repeated disconnects or reconnect attempts.Fix:
If needed, re-link with channels login.If the gateway is already running for the account, use the gateway login flow when available. A direct CLI login against the same auth directory will refuse to start while another openclaw process owns the socket lock.
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)
  • duplicate keys in openclaw.json (JSON5): later entries override earlier ones, so keep a single groupPolicy per scope
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, chunkNewlinePacking, mediaMaxMb, sendReadReceipts, ackReaction
  • delivery target: defaultTo
  • message behavior: debounceMs, messagePrefix, selfChatMode
  • 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
  • streaming: blockStreaming, blockStreamingCoalesce
  • formatting: markdown, responsePrefix
  • heartbeat: heartbeat