Skip to main content

WhatsApp

WednesdayAI connects to WhatsApp via the WhatsApp Web protocol (Baileys). The gateway links a WhatsApp number to the gateway process, which then sends and receives on that number’s behalf. The whatsapp bundled extension owns platform dependencies, login, session inspection, outbound delivery, polls, and reactions.
WhatsApp requires the gateway to run on Node.js (not Bun). Bun is flagged as incompatible for stable WhatsApp operation. Run openclaw doctor to verify runtime compatibility.

Prerequisites

  • A WhatsApp-capable phone number (a dedicated number is recommended — see Deployment patterns)
  • Node.js 24+ on the gateway host
  • The phone holding the linked number online during linking

Installation

The WhatsApp plugin ships bundled but requires its runtime dependency (@whiskeysockets/baileys) to be installed separately:
openclaw plugins install whatsapp
After install, restart the gateway. If deps are missing, openclaw plugins list shows deps missing for the whatsapp plugin and the gateway emits a PLUGIN_DEPS_MISSING warning at boot.
If the automated install fails, navigate to ~/.openclaw/extensions/whatsapp and run npm install --omit=dev manually.

Quick setup

1

Configure access policy

// ~/.openclaw/openclaw.json
{
  channels: {
    whatsapp: {
      enabled: true,
      dmPolicy: "pairing",           // pairing | allowlist | open | disabled
      allowFrom: ["+15555550123"],   // E.164 numbers allowed to message the bot
      groupPolicy: "allowlist",
      groupAllowFrom: ["+15555550123"],
    },
  },
}
2

Link the WhatsApp number

openclaw channels login --channel whatsapp
Scan the QR with the WhatsApp app on the phone holding the number. The QR expires after about 2 minutes — re-run if it times out.For a named account: openclaw channels login --channel whatsapp --account work.If the gateway is already running for this account, use the gateway’s login flow — a direct CLI login against the same auth directory will refuse to start while the gateway owns the socket lock.
3

Start the gateway and approve pairing

openclaw gateway run
openclaw pairing list whatsapp
openclaw pairing approve whatsapp <CODE>
Pairing codes expire after 1 hour. Pending requests are capped at 3 per channel.
4

Verify

openclaw channels status --channel whatsapp
openclaw status --deep

Deployment patterns

Supported but adds operational complexity. The onboarding wizard sets a self-chat-friendly baseline when you choose personal-number mode:
{
  channels: {
    whatsapp: {
      dmPolicy: "allowlist",
      allowFrom: ["+15555550123"],  // your own number
      selfChatMode: true,
    },
  },
}
With selfChatMode: true (or when the linked self number is in allowFrom), self-chat read receipts are skipped and self-mention triggers are suppressed. If messages.responsePrefix is unset, self-chat replies default to [WednesdayAI].

Access control

DM policy

dmPolicyEffect
"pairing"New senders get a one-time pairing code; ignored until approved (default)
"allowlist"Only numbers in allowFrom can initiate DMs
"open"Any sender allowed (requires allowFrom: ["*"])
"disabled"Bot ignores all DMs
allowFrom accepts E.164-style numbers. Approved pairings are persisted in the allow-store and merged with configured allowFrom at runtime. If no allowFrom is configured, the linked self number is allowed by default. Per-account override: channels.whatsapp.accounts.<id>.dmPolicy and accounts.<id>.allowFrom take precedence over channel-level defaults for that account.

Group policy

{
  channels: {
    whatsapp: {
      groupPolicy: "allowlist",       // open | allowlist | disabled
      groupAllowFrom: ["+15555550123"],
      groups: {
        "120363000000000000@g.us": { requireMention: false },
      },
    },
  },
}
Access has two layers:
  1. Group membership — if groups is present, it acts as an allowlist; omit it to allow all groups. "*" allows all.
  2. Sender policygroupPolicy + groupAllowFrom. If groupAllowFrom is unset, the runtime falls back to allowFrom.
If no channels.whatsapp config block exists, the runtime group-policy fallback is "allowlist" (with a warning log), even if channels.defaults.groupPolicy is set.

Mention gating

Group messages require a mention by default. To allow responses without a mention:
{ channels: { whatsapp: { groups: { "*": { requireMention: false } } } } }
Mention detection includes explicit WhatsApp mentions, configured mentionPatterns, and reply-to-bot detection. Per-session activation: /activation mention or /activation always (owner-gated). Security note: a quote/reply satisfies mention gating but does not grant sender authorization. Non-allowlisted senders are still blocked with groupPolicy: "allowlist" even if they reply to an allowlisted user.

Message handling

Group history injection

When the bot is triggered in a group after a gap, unprocessed messages are injected as context:
{
  channels: {
    whatsapp: {
      historyLimit: 50,  // default; set 0 to disable
    },
  },
}
Injected history is wrapped in [Chat messages since your last reply - for context] / [Current message - respond to this] markers.

Inbound media

Media-only messages are normalized to placeholders: <media:image>, <media:video>, <media:audio>, <media:document>, <media:sticker>. Location and contact payloads are converted to text before routing.

Delivery settings

{
  channels: {
    whatsapp: {
      textChunkLimit: 4000,
      chunkMode: "length",            // length | newline (paragraph-aware)
      chunkNewlinePacking: true,      // when chunkMode: "newline", pack multiple paragraphs into one message up to the limit (default: true)
      mediaMaxMb: 50,                 // inbound media save cap
      sendReadReceipts: true,
    },
  },
}
Per-account override for sendReadReceipts:
{ channels: { whatsapp: { accounts: { work: { sendReadReceipts: false } } } } }
Self-chat turns skip read receipts even when globally enabled.

Acknowledgment reactions

React to inbound messages before the reply is sent:
{
  channels: {
    whatsapp: {
      ackReaction: {
        emoji: "👀",
        direct: true,               // boolean: react to DMs
        group: "mentions",          // always | mentions | never
      },
    },
  },
}
Failures are logged but do not block reply delivery. group: "mentions" reacts only on mention-triggered turns; session activation always bypasses this check.

Actions and config writes

Gate outbound actions per channel:
{
  channels: {
    whatsapp: {
      actions: {
        reactions: true,   // allow the agent to send reaction emojis
        polls: true,       // allow the agent to create polls
        sendMessage: true, // allow the agent to send messages (default: true)
      },
      configWrites: true,  // allow channel-initiated config writes (default: true)
    },
  },
}

Multi-account setup

{
  channels: {
    whatsapp: {
      dmPolicy: "allowlist",
      accounts: {
        personal: { authDir: "~/.openclaw/credentials/whatsapp/personal" },
        work:     { authDir: "~/.openclaw/credentials/whatsapp/work" },
      },
    },
  },
}
openclaw channels login --channel whatsapp --account personal
openclaw channels login --channel whatsapp --account work
Default account selection: default if present, otherwise the first configured account id (sorted). Per-account dmPolicy, allowFrom, sendReadReceipts, and other overrides are supported.

Credentials and storage

WhatsApp credentials live at ~/.openclaw/credentials/whatsapp/<accountId>/creds.json. A recent mtime indicates an active session; a stale or missing file means the account is logged out. Active sockets create a .socket-owner.lock next to credentials to prevent cross-process collisions. Stale locks from dead processes are removed automatically via PID checks. To log out an account:
openclaw channels logout --channel whatsapp
openclaw channels logout --channel whatsapp --account work
Logout closes active QR sockets, stops gateway listeners for the account, drains queued credential writes, then removes Baileys auth files. oauth.json and unrelated state are preserved.

Disabling without removing config

{ channels: { whatsapp: { enabled: false } } }
Restart the gateway to apply: systemctl --user restart openclaw-gateway.

Troubleshooting

openclaw channels logout --channel whatsapp
openclaw channels login --channel whatsapp
The phone must be online. The QR expires after about 2 minutes. If the gateway is running for this account, stop it first — a direct CLI login is refused while the gateway owns the socket lock.
Repeated disconnects or reconnect attempts after a successful link:
openclaw doctor
openclaw logs --follow
If the account shows a reconnect loop, re-link: openclaw channels logout --channel whatsapp && openclaw channels login --channel whatsapp.
A conflicting WhatsApp Web session is open in a browser. Close all other WhatsApp Web browser sessions, then relink. Status 515 auto-retries once after pairing — this is normal.
Check in this order:
  1. groupPolicy allows the group
  2. groupAllowFrom includes the sender (or falls back to allowFrom)
  3. groups allowlist includes the group JID (or is omitted to allow all)
  4. Mention gating: requireMention: false, or the message mentions the bot
  5. No duplicate keys in openclaw.json (openclaw doctor) — later entries override earlier ones silently
Outbound sends fail when no active gateway listener exists for the target account:
openclaw gateway status
openclaw channels status --channel whatsapp
Run openclaw plugins install whatsapp to reinstall the plugin and its dependencies. If that fails, navigate to ~/.openclaw/extensions/whatsapp and run npm install --omit=dev manually.

Configuration reference

Full field reference for all WhatsApp config options (access, delivery, multi-account, operations, session): High-signal fields: dmPolicy, allowFrom, groupPolicy, groupAllowFrom, groups, textChunkLimit, chunkMode, mediaMaxMb, sendReadReceipts, ackReaction, historyLimit, actions, configWrites, accounts.<id>.*