Status: production-ready via WhatsApp Web (Baileys). Gateway owns linked session(s).Documentation Index
Fetch the complete documentation index at: https://docs.openclaw.ai/llms.txt
Use this file to discover all available pages before exploring further.
Install (on demand)
- Onboarding (
openclaw onboard) andopenclaw channels add --channel whatsappprompt to install the WhatsApp plugin the first time you select it. openclaw channels login --channel whatsappalso offers the install flow when the plugin is not present yet.- Dev channel + git checkout: defaults to the local plugin path.
- Stable/Beta: defaults to the npm package
@openclaw/whatsapp.
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
Link WhatsApp (QR)
OpenClaw recommends running WhatsApp on a separate number when possible. (The channel metadata and setup flow are optimized for that setup, but personal-number setups are also supported.)
Deployment patterns
Dedicated number (recommended)
Dedicated number (recommended)
This is the cleanest operational mode:
- separate WhatsApp identity for OpenClaw
- clearer DM allowlists and routing boundaries
- lower chance of self-chat confusion
Personal-number fallback
Personal-number fallback
Onboarding supports personal-number mode and writes a self-chat-friendly baseline:
dmPolicy: "allowlist"allowFromincludes your personal numberselfChatMode: true
allowFrom.WhatsApp Web-only channel scope
WhatsApp Web-only channel scope
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.
- The reconnect watchdog uses WhatsApp Web transport activity, not only inbound app-message volume, so a quiet linked-device session is not restarted solely because nobody has sent a message recently. A longer application-silence cap still forces a reconnect if transport frames keep arriving but no application messages are handled for the watchdog window.
- Baileys socket timings are explicit under
web.whatsapp.*:keepAliveIntervalMscontrols WhatsApp Web application pings,connectTimeoutMscontrols the opening handshake timeout, anddefaultQueryTimeoutMscontrols Baileys query timeouts. - 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; defaultmaincollapses DMs to the agent main session). - Group sessions are isolated (
agent:<agentId>:whatsapp:group:<jid>). - WhatsApp Web transport honors standard proxy environment variables on the gateway host (
HTTPS_PROXY,HTTP_PROXY,NO_PROXY/ lowercase variants). Prefer host-level proxy config over channel-specific WhatsApp proxy settings. - When
messages.removeAckAfterReplyis enabled, OpenClaw clears the WhatsApp ack reaction after a visible reply is delivered.
Plugin hooks and privacy
WhatsApp inbound messages can contain personal message content, phone numbers, group identifiers, sender names, and session correlation fields. For that reason, WhatsApp does not broadcast inboundmessage_received hook payloads to plugins
unless you explicitly opt in:
Access control and activation
- DM policy
- Group policy + allowlists
- Mentions + /activation
channels.whatsapp.dmPolicy controls direct chat access:pairing(default)allowlistopen(requiresallowFromto 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
- OpenClaw never auto-pairs outbound
fromMeDMs (messages you send to yourself from the linked device)
Personal-number and self-chat behavior
When the linked self number is also present inallowFrom, 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.responsePrefixis unset, self-chat replies default to[{identity.name}]or[openclaw]
Message normalization and context
Inbound envelope + reply context
Inbound envelope + reply 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).Media placeholders and location/contact extraction
Media placeholders and location/contact extraction
Media-only inbound messages are normalized with placeholders such as:
<media:image><media:video><media:audio><media:document><media:sticker>
<media:audio>, so saying the bot mention in the voice note can
trigger the reply. If the transcript still does not mention the bot, the
transcript is kept in pending group history instead of the raw placeholder.Location bodies use terse coordinate text. Location labels/comments and contact/vCard details are rendered as fenced untrusted metadata, not inline prompt text.Pending group history injection
Pending group history injection
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 0disables
[Chat messages since your last reply - for context][Current message - respond to this]
Read receipts
Read receipts
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
Text chunking
Text chunking
- default chunk limit:
channels.whatsapp.textChunkLimit = 4000 channels.whatsapp.chunkMode = "length" | "newline"newlinemode prefers paragraph boundaries (blank lines), then falls back to length-safe chunking
Outbound media behavior
Outbound media behavior
- supports image, video, audio (PTT voice-note), and document payloads
- audio media is sent through the Baileys
audiopayload withptt: true, so WhatsApp clients render it as a push-to-talk voice note - reply payloads preserve
audioAsVoice; TTS voice-note output for WhatsApp stays on this PTT path even when the provider returns MP3 or WebM - native Ogg/Opus audio is sent as
audio/ogg; codecs=opusfor voice-note compatibility - non-Ogg audio, including Microsoft Edge TTS MP3/WebM output, is transcoded with
ffmpegto 48 kHz mono Ogg/Opus before PTT delivery /tts latestsends the latest assistant reply as one voice note and suppresses repeat sends for the same reply;/tts chat on|off|defaultcontrols auto-TTS for the current WhatsApp chat- animated GIF playback is supported via
gifPlayback: trueon video sends - captions are applied to the first media item when sending multi-media reply payloads, except PTT voice notes send the audio first and visible text separately because WhatsApp clients do not render voice-note captions consistently
- media source can be HTTP(S),
file://, or local paths
Media size limits and fallback behavior
Media size limits and fallback behavior
- inbound media save cap:
channels.whatsapp.mediaMaxMb(default50) - outbound media send cap:
channels.whatsapp.mediaMaxMb(default50) - per-account overrides use
channels.whatsapp.accounts.<accountId>.mediaMaxMb - 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
Reply quoting
WhatsApp supports native reply quoting, where outbound replies visibly quote the inbound message. Control it withchannels.whatsapp.replyToMode.
| Value | Behavior |
|---|---|
"off" | Never quote; send as a plain message |
"first" | Quote only the first outbound reply chunk |
"all" | Quote every outbound reply chunk |
"batched" | Quote queued batched replies while leaving immediate replies unquoted |
"off". Per-account overrides use channels.whatsapp.accounts.<id>.replyToMode.
Reaction level
channels.whatsapp.reactionLevel controls how broadly the agent uses emoji reactions on WhatsApp:
| Level | Ack reactions | Agent-initiated reactions | Description |
|---|---|---|---|
"off" | No | No | No reactions at all |
"ack" | Yes | No | Ack reactions only (pre-reply receipt) |
"minimal" | Yes | Yes (conservative) | Ack + agent reactions with conservative guidance |
"extensive" | Yes | Yes (encouraged) | Ack + agent reactions with encouraged guidance |
"minimal".
Per-account overrides use channels.whatsapp.accounts.<id>.reactionLevel.
Acknowledgment reactions
WhatsApp supports immediate ack reactions on inbound receipt viachannels.whatsapp.ackReaction.
Ack reactions are gated by reactionLevel — they are suppressed when reactionLevel is "off".
- sent immediately after inbound is accepted (pre-reply)
- failures are logged but do not block normal reply delivery
- group mode
mentionsreacts on mention-triggered turns; group activationalwaysacts as bypass for this check - WhatsApp uses
channels.whatsapp.ackReaction(legacymessages.ackReactionis not used here)
Multi-account and credentials
Account selection and defaults
Account selection and defaults
- account ids come from
channels.whatsapp.accounts - default account selection:
defaultif present, otherwise first configured account id (sorted) - account ids are normalized internally for lookup
Credential paths and legacy compatibility
Credential paths and legacy compatibility
- 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
Logout behavior
Logout behavior
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.reactionschannels.whatsapp.actions.polls
- Channel-initiated config writes are enabled by default (disable via
channels.whatsapp.configWrites=false).
Troubleshooting
Not linked (QR required)
Not linked (QR required)
Symptom: channel status reports not linked.Fix:
Linked but disconnected / reconnect loop
Linked but disconnected / reconnect loop
Symptom: linked account with repeated disconnects or reconnect attempts.Quiet accounts can stay connected past the normal message timeout; the watchdog
restarts when WhatsApp Web transport activity stops, the socket closes, or
application-level activity stays silent beyond the longer safety window.If logs show repeated Fix:If needed, re-link with
status=408 Request Time-out Connection was lost, tune
Baileys socket timings under web.whatsapp. Start by shortening
keepAliveIntervalMs below your network’s idle timeout and increasing
connectTimeoutMs on slow or lossy links:channels login.QR login times out behind a proxy
QR login times out behind a proxy
Symptom:
openclaw channels login --channel whatsapp fails before showing a usable QR code with status=408 Request Time-out or a TLS socket disconnect.WhatsApp Web login uses the gateway host’s standard proxy environment (HTTPS_PROXY, HTTP_PROXY, lowercase variants, and NO_PROXY). Verify the gateway process inherits the proxy env and that NO_PROXY does not match mmg.whatsapp.net.No active listener when sending
No active listener when sending
Outbound sends fail fast when no active gateway listener exists for the target account.Make sure gateway is running and the account is linked.
Group messages unexpectedly ignored
Group messages unexpectedly ignored
Check in this order:
groupPolicygroupAllowFrom/allowFromgroupsallowlist entries- mention gating (
requireMention+ mention patterns) - duplicate keys in
openclaw.json(JSON5): later entries override earlier ones, so keep a singlegroupPolicyper scope
Bun runtime warning
Bun runtime warning
WhatsApp gateway runtime should use Node. Bun is flagged as incompatible for stable WhatsApp/Telegram gateway operation.
System prompts
WhatsApp supports Telegram-style system prompts for groups and direct chats via thegroups and direct maps.
Resolution hierarchy for group messages:
The effective groups map is determined first: if the account defines its own groups, it fully replaces the root groups map (no deep merge). Prompt lookup then runs on the resulting single map:
- Group-specific system prompt (
groups["<groupId>"].systemPrompt): used when the specific group entry exists in the map and itssystemPromptkey is defined. IfsystemPromptis an empty string (""), the wildcard is suppressed and no system prompt is applied. - Group wildcard system prompt (
groups["*"].systemPrompt): used when the specific group entry is absent from the map entirely, or when it exists but defines nosystemPromptkey.
direct map is determined first: if the account defines its own direct, it fully replaces the root direct map (no deep merge). Prompt lookup then runs on the resulting single map:
- Direct-specific system prompt (
direct["<peerId>"].systemPrompt): used when the specific peer entry exists in the map and itssystemPromptkey is defined. IfsystemPromptis an empty string (""), the wildcard is suppressed and no system prompt is applied. - Direct wildcard system prompt (
direct["*"].systemPrompt): used when the specific peer entry is absent from the map entirely, or when it exists but defines nosystemPromptkey.
dms remains the lightweight per-DM history override bucket (dms.<id>.historyLimit). Prompt overrides live under direct.groups is intentionally suppressed for all accounts in a multi-account setup — even accounts that define no groups of their own — to prevent a bot from receiving group messages for groups it does not belong to. WhatsApp does not apply this guard: root groups and root direct are always inherited by accounts that define no account-level override, regardless of how many accounts are configured. In a multi-account WhatsApp setup, if you want per-account group or direct prompts, define the full map under each account explicitly rather than relying on root-level defaults.
Important behavior:
channels.whatsapp.groupsis both a per-group config map and the chat-level group allowlist. At either the root or account scope,groups["*"]means “all groups are admitted” for that scope.- Only add a wildcard group
systemPromptwhen you already want that scope to admit all groups. If you still want only a fixed set of group IDs to be eligible, do not usegroups["*"]for the prompt default. Instead, repeat the prompt on each explicitly allowlisted group entry. - Group admission and sender authorization are separate checks.
groups["*"]widens the set of groups that can reach group handling, but it does not by itself authorize every sender in those groups. Sender access is still controlled separately bychannels.whatsapp.groupPolicyandchannels.whatsapp.groupAllowFrom. channels.whatsapp.directdoes not have the same side effect for DMs.direct["*"]only provides a default direct-chat config after a DM is already admitted bydmPolicyplusallowFromor pairing-store rules.
Configuration reference pointers
Primary reference: High-signal WhatsApp fields:- access:
dmPolicy,allowFrom,groupPolicy,groupAllowFrom,groups - delivery:
textChunkLimit,chunkMode,mediaMaxMb,sendReadReceipts,ackReaction,reactionLevel - multi-account:
accounts.<id>.enabled,accounts.<id>.authDir, account-level overrides - operations:
configWrites,debounceMs,web.enabled,web.heartbeatSeconds,web.reconnect.*,web.whatsapp.* - session behavior:
session.dmScope,historyLimit,dmHistoryLimit,dms.<id>.historyLimit - prompts:
groups.<id>.systemPrompt,groups["*"].systemPrompt,direct.<id>.systemPrompt,direct["*"].systemPrompt