WhatsApp Channel Setup and Operations

Learn to install and configure the WhatsApp channel via the OpenClaw plugin, covering pairing, delivery, and troubleshooting for production use.

Read this when

  • Working on WhatsApp/web channel behavior or inbox routing

Status: ready for production through WhatsApp Web (Baileys). The gateway manages the linked session or sessions; a dedicated Twilio WhatsApp channel is not involved.

Install

openclaw onboard and openclaw channels add --channel whatsapp ask you to install the plugin when you pick it for the first time; openclaw channels login --channel whatsapp presents the same installation process if the plugin is absent. Development builds point to the local plugin path; stable and beta releases install @openclaw/whatsapp from ClawHub first, with npm as the fallback. The WhatsApp runtime lives outside the core OpenClaw npm package, so its runtime dependencies remain with the external plugin. Manual installation:

openclaw plugins install clawhub:@openclaw/whatsapp

Reserve the bare npm package (@openclaw/whatsapp) for the registry fallback; pin an exact version only when you need a reproducible install.

Quick setup

Configure access policy

{
  channels: {
    whatsapp: {
      dmPolicy: "pairing",
      allowFrom: ["+15551234567"],
      groupPolicy: "allowlist",
      groupAllowFrom: ["+15551234567"],
    },
  },
}
openclaw channels login --channel whatsapp

Login happens only through a QR code. On remote or headless hosts, make sure you have a dependable way to get the live QR to the phone before you start login; terminal-rendered QRs, screenshots, or chat attachments may expire while in transit.

For one specific account:

openclaw channels login --channel whatsapp --account work

To attach an existing or custom auth directory before login:

openclaw channels add --channel whatsapp --account work --auth-dir /path/to/wa-auth
openclaw channels login --channel whatsapp --account work

Start the gateway

openclaw gateway

Approve the first DM access request (pairing mode)

Go to Settings โ†’ Channels โ†’ DM access requests, locate the WhatsApp account, and approve the sender. Prefer the CLI? Then:

openclaw pairing list whatsapp
openclaw pairing approve whatsapp <CODE>

DM access requests expire after 1 hour; each account can have at most 3 pending requests. This approval is separate from the WhatsApp login QR that links the account itself.

Note

Using a separate WhatsApp number is recommended (setup and metadata are tuned for it), but personal-number and self-chat setups work fully.

Deployment patterns

Dedicated number (recommended)

  • a separate WhatsApp identity for OpenClaw
  • clearer DM allowlists and routing boundaries
  • lower chance of self-chat confusion
{
  channels: {
    whatsapp: {
      dmPolicy: "allowlist",
      allowFrom: ["+15551234567"],
    },
  },
}

Personal-number fallback

Onboarding supports personal-number mode and writes a self-chat-friendly baseline: dmPolicy: "allowlist", allowFrom including your own number, selfChatMode: true. Runtime self-chat protections key off the linked self number plus allowFrom.

Runtime model

  • The gateway owns the WhatsApp socket and reconnect loop.
  • A watchdog tracks two signals independently: raw WhatsApp Web transport activity and application-message activity. A quiet-but-connected session is not restarted just because no message arrived recently; it forces reconnect only when transport frames stop arriving for a fixed internal window (not user-configurable) or application messages stay silent past 4x the normal message timeout. Right after a reconnect for a recently active session, that first window uses the shorter normal message timeout instead of the 4x window. OpenClaw can auto-reply to offline messages that Baileys delivers early in that reconnect, bounded by the inbound message-ID dedupe lifetime; initial startup keeps the short stale-history guard.
  • Outbound sends require an active WhatsApp listener for the target account; sends fail fast otherwise.
  • Group sends attach native mention metadata for @+<digits> and @<digits> tokens (in text and media captions) when the token matches current participant metadata, including LID-backed groups.
  • Status and broadcast chats (@status, @broadcast) are ignored.
  • Direct chats use DM session rules (session.dmScope; default main collapses DMs into the agent main session). With the default session.groupScope: "per-group", group sessions are isolated per JID (agent:<agentId>:whatsapp:group:<jid>).
  • WhatsApp Channels/Newsletters can be explicit outbound targets via their native @newsletter JID, using channel session metadata (agent:<agentId>:whatsapp:channel:<jid>) rather than DM semantics.
  • 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 per-channel settings.

Call the current requester with MeowCaller (experimental)

The plugin can expose whatsapp_call in WhatsApp-originated agent turns. It uses MeowCaller to place a WhatsApp voice call to the current authorized requester and play an OpenClaw TTS message after they answer. The tool has no destination-number parameter, so a prompt cannot redirect the call. Disabled by default.

Warning

MeowCaller is experimental, has no tagged release, and uses a separately paired whatsmeow linked-device session, it cannot reuse the plugin's Baileys credentials. Pairing adds another linked device to the same WhatsApp account; scan with the identity used by OpenClaw. Personal-number/self-chat mode cannot call itself; use a dedicated OpenClaw number to call your personal number.

Enable experimental calls

Add actions.calls: true to the WhatsApp channel config and restart the gateway:

{
  "channels": {
    "whatsapp": {
      "actions": {
        "calls": true
      }
    }
  }
}

When absent or false, OpenClaw does not expose the whatsapp_call tool.

Install the reviewed MeowCaller CLI

The adapter expects a meowcaller executable on the gateway host's PATH. Until MeowCaller PR #7 merges, build the reviewed branch:

git clone --branch feat/send-only-notify https://github.com/steipete/meowcaller.git
cd meowcaller
git checkout 752050471fc2bf7a8cdfbf7dbd3cd4e865d85d3f
mkdir -p "$HOME/.local/bin"
go build -o "$HOME/.local/bin/meowcaller" ./cmd/meowcaller

Ensure $HOME/.local/bin is present on the gateway service's PATH. This version includes explicit pair and send-only notify commands; notify does not activate any microphone, speaker, video device, or diagnostic capture. Do not use the upstream example CLI's play command as a replacement.

Pair the MeowCaller linked device

Have the WhatsApp agent verify call setup (the whatsapp_call status action reports the account-specific state directory and pairing command). For the default account:

state_dir="$HOME/.openclaw/credentials/whatsapp-calls/default"
mkdir -p "$state_dir"
chmod 700 "$state_dir"
meowcaller pair --store "$state_dir/wa-voip.db"

Execute this interactively, scan the QR from WhatsApp > Linked devices, and wait for MeowCaller linked device ready. Keep wa-voip.db confidential, as it represents the MeowCaller session. Non-default accounts receive their own store path from the status action; on Windows, run its PowerShell command.

Configure TTS and call from WhatsApp

Set up a telephony-capable TTS provider, restart the gateway, then submit a request like Call me and say the build finished. The tool derives the sender from trusted inbound context, generates a temporary private WAV file, invokes MeowCaller for a limited call window, and removes the audio file afterward. OpenClaw passes the account's store explicitly, waits for a zero exit status after answer/playback/hangup, and considers a timeout or nonzero exit as a failed tool call.

Constraints: only one-to-one outbound audio calls, no arbitrary destination numbers, no shared auth with the chat connection, no self-calls from personal-number/self-chat mode, synthesized audio limited to 60 seconds, no handset-side audibility receipt beyond MeowCaller's answer/playback/hangup completion, and OpenClaw terminates the companion process after a bounded 115-175 second window (covering MeowCaller's connection, answer, playback, and shutdown phases).

Approval prompts

WhatsApp can display exec and plugin approval prompts as ๐Ÿ‘/๐Ÿ‘Ž reactions, governed by the top-level approval forwarding config:

{
  approvals: {
    exec: {
      enabled: true,
      mode: "session",
    },
    plugin: {
      enabled: true,
      mode: "targets",
      targets: [{ channel: "whatsapp", to: "+15551234567" }],
    },
  },
}

approvals.exec and approvals.plugin operate separately; enabling WhatsApp as a channel only connects the transport and transmits nothing unless the matching approval family is enabled and routed there. Session mode delivers native emoji approvals only for approvals originating from WhatsApp. Target mode relies on the shared forwarding pipeline for explicit targets and does not generate separate approver-DM fanout.

WhatsApp approval reactions require explicit approvers in allowFrom (or "*"). defaultTo establishes ordinary default message targets, not an approver list. Manual /approve commands still undergo the normal WhatsApp sender-authorization path before approval resolution.

Question reactions

For an ask_user prompt with one non-secret, single-select question and one to four options, WhatsApp displays 1๏ธโƒฃ through 4๏ธโƒฃ next to the option labels. React to the delivered prompt with the matching number to answer it. OpenClaw maps the number to the canonical option through the Gateway; stale or duplicate taps are ignored. Multi-question, multi-select, and free-text prompts remain text-reply-only. Normal WhatsApp DM/group admission rules authorize the reacting sender.

Plugin hooks and privacy

Inbound WhatsApp messages may contain personal content, phone numbers, group identifiers, sender names, and session correlation fields. WhatsApp does not broadcast inbound message_received hook payloads to plugins unless you opt in:

{
  channels: {
    whatsapp: {
      pluginHooks: {
        messageReceived: true,
      },
    },
  },
}

Scope the opt-in to one account under channels.whatsapp.accounts.<id>.pluginHooks.messageReceived. Only enable this for plugins you trust with inbound WhatsApp content and identifiers.

Access control and activation

DM policy

channels.whatsapp.dmPolicy:

ValueBehavior
pairing (default)Unknown senders request pairing; owner approves
allowlistOnly allowFrom senders admitted
openRequires allowFrom to include "*"
disabledBlock all DMs

allowFrom accepts E.164-style numbers (normalized internally). It functions solely as a DM sender access-control list, not gating explicit outbound sends to group JIDs or @newsletter channel JIDs.

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

Runtime notes:

  • pairings persist in the channel allow-store and merge with configured allowFrom
  • scheduled automation and heartbeat recipient fallback use explicit delivery targets or configured allowFrom; DM pairing approvals are not implicit cron/heartbeat recipients
  • if no allowlist is configured, the linked self number is allowed by default
  • OpenClaw never auto-pairs outbound fromMe DMs (messages you send yourself from the linked device)

Group policy and allowlists

Group access involves two layers:

  1. Group membership allowlist (channels.whatsapp.groups): if groups is omitted, all groups are eligible; if present, it acts as a group allowlist ("*" admits all).
  2. Group sender policy (channels.whatsapp.groupPolicy + groupAllowFrom): open bypasses the sender allowlist, allowlist requires a groupAllowFrom (or *) match, disabled blocks all group inbound.

If groupAllowFrom is unset, sender checks fall back to allowFrom when it has entries. Sender allowlists are evaluated before mention/reply activation.

If no channels.whatsapp block is present anywhere, runtime uses groupPolicy: "allowlist" as the fallback (logging a warning), regardless of whether channels.defaults.groupPolicy points elsewhere.

Note

Group-membership resolution includes a single-account safety net: when exactly one WhatsApp account is configured and its accounts.<id>.groups is an explicit empty object ({}), that value is interpreted as "not set" and the root channels.whatsapp.groups map is used instead, avoiding a silent block on all groups. With 2+ accounts in place, an explicit empty account map remains empty and no fallback occurs, so one account can disable all groups intentionally without impacting others.

Mentions and /activation

By default, group replies demand a mention. Mention detection covers:

  • direct WhatsApp mentions of the bot identity
  • configured mention regex patterns (agents.entries.*.groupChat.mentionPatterns, with messages.groupChat.mentionPatterns as fallback)
  • transcripts from inbound voice notes for authorized group messages
  • implicit reply-to-bot detection (when the reply sender matches the bot identity)

Security: a quote or reply only satisfies mention gating, it does not grant sender authorization. With groupPolicy: "allowlist", non-allowlisted senders remain blocked even when replying to a message from an allowlisted user.

Session-level activation command: /activation mention or /activation always. This alters session state (not global config) and is gated to the owner.

Configured ACP bindings

WhatsApp enables persistent ACP bindings through top-level bindings[]:

{
  bindings: [
    {
      type: "acp",
      agentId: "codex",
      match: {
        channel: "whatsapp",
        accountId: "work",
        peer: { kind: "direct", id: "+15555550123" },
      },
    },
    {
      type: "acp",
      agentId: "codex",
      match: {
        channel: "whatsapp",
        accountId: "work",
        peer: { kind: "group", id: "120363424282127706@g.us" },
      },
    },
  ],
}

Direct chats match E.164 numbers; groups match WhatsApp group JIDs. Group allowlists, sender policy, and mention/activation gating all run before OpenClaw ensures the bound ACP session exists. A matched binding owns the route, so broadcast groups do not fan that turn out to ordinary WhatsApp sessions.

Personal-number and self-chat behavior

When the linked self number also appears in allowFrom, self-chat safeguards kick in: read receipts are skipped for self-chat turns, mention-JID auto-trigger behavior that would ping yourself is ignored, and replies default to [{identity.name}] (or [openclaw]) when the channel/account responsePrefix is not set.

Message normalization and context

Inbound envelope and reply context

Incoming messages get wrapped in the shared inbound envelope. A quoted reply appends context in this format:

[Replying to <sender> id:<stanzaId>]
<quoted body or media placeholder>
[/Replying]

Reply metadata (ReplyToId, ReplyToBody, ReplyToSender, sender JID/E.164) gets populated whenever available. If the quoted target is downloadable media, OpenClaw stores it through the normal inbound media path and exposes MediaPath/MediaType so the agent can inspect it directly, rather than seeing only <media:image>.

Media placeholders and location/contact extraction

Media-only messages normalize to placeholders: <media:image>, <media:video>, <media:audio>, <media:document>, <media:sticker>.

Authorized group voice notes get transcribed before mention gating when the body is only <media:audio>, so saying the bot mention in the voice note can trigger the reply. If the transcript still lacks the bot mention, it stays in pending group history instead of the raw placeholder.

Location bodies render as terse coordinate text. Location labels/comments and contact/vCard details render as fenced untrusted metadata, not inline prompt text.

Pending group history injection

Unprocessed group messages buffer and inject 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] and [Current message - respond to this].

Read receipts

Enabled by default for accepted inbound messages. Disable globally:

{ channels: { whatsapp: { sendReadReceipts: false } } }

Per-account override: channels.whatsapp.accounts.<id>.sendReadReceipts. Self-chat turns skip read receipts even when globally enabled.

Delivery, chunking, and media

Text chunking

  • default chunk limit: channels.whatsapp.textChunkLimit = 4000
  • channels.whatsapp.streaming.chunkMode = "length" | "newline"; newline prefers paragraph boundaries (blank lines), then falls back to length-safe chunking

Outbound media behavior

  • image, video, audio (PTT voice-note), and document payloads are all supported
  • audio uses the Baileys audio payload with ptt: true, which renders as a push-to-talk voice note; audioAsVoice is retained on reply payloads so TTS voice-note output stays on this path regardless of the provider's source format
  • native Ogg/Opus audio is delivered as audio/ogg; codecs=opus; any other format (including Microsoft Edge TTS MP3/WebM output) gets transcoded with ffmpeg to 48 kHz mono Ogg/Opus before PTT delivery
  • the latest assistant reply is sent as a single voice note via /tts latest, which also suppresses repeat sends for the same reply; /tts chat on|off|default manages auto-TTS for the current chat
  • animated GIF playback is enabled by gifPlayback: true on video sends
  • outbound images, GIFs, and videos are routed through the Baileys document payload via forceDocument/asDocument to bypass WhatsApp's media compression, keeping the resolved filename and MIME type intact
  • captions apply to the first media item in a multi-media reply, except for PTT voice notes: the audio goes first with no caption, then the caption follows as a separate text message (WhatsApp clients do not render voice-note captions consistently)
  • when a later captioned reply repeats pending attachments, only attachments accepted by WhatsApp replace their pending copies; unmatched attachments stay queued for delivery
  • media source can be HTTP(S), file://, or a local path

Media size limits and fallback behavior

  • inbound save cap and outbound send cap: channels.whatsapp.mediaMaxMb (default 50)
  • per-account override: channels.whatsapp.accounts.<id>.mediaMaxMb
  • images auto-optimize (resize/quality sweep) to fit limits unless forceDocument/asDocument requests document delivery
  • on media send failure, the first-item fallback sends a text warning instead of dropping the response silently

Reply quoting

Native reply quoting (outbound replies visibly quote the inbound message) is controlled by channels.whatsapp.replyToMode:

ValueBehavior
"off" (default)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; leave immediate replies unquoted

Per-account override: channels.whatsapp.accounts.<id>.replyToMode.

{ channels: { whatsapp: { replyToMode: "first" } } }

Reaction level

How broadly the agent uses emoji reactions is governed by channels.whatsapp.reactionLevel:

LevelAck reactionsAgent-initiated reactions
"off"NoNo
"ack"YesNo
"minimal" (default)YesYes, conservative guidance
"extensive"YesYes, encouraged guidance

Per-account override: channels.whatsapp.accounts.<id>.reactionLevel.

{ channels: { whatsapp: { reactionLevel: "ack" } } }

Acknowledgment reactions

An immediate reaction on inbound receipt is sent by messages.ackReaction, gated by the active WhatsApp account's reactionLevel (suppressed when "off"). Direct messages, groups, or both are selected via messages.ackReactionScope:

{
  messages: {
    ackReaction: "๐Ÿ‘€",
    ackReactionScope: "group-mentions", // all | direct | group-all | group-mentions | off
  },
}

Notes: the reaction fires right after inbound is accepted (pre-reply); omit messages.ackReaction or set it to "" for no acknowledgment. Failures are logged but do not block reply delivery. The default scope is "group-mentions"; use "all" for direct messages and all eligible groups. In a group whose activation is always, "group-mentions" acks every message rather than only mention-triggered turns, because activation stands in for the mention check.

Lifecycle status reactions

Set messages.statusReactions.enabled: true to let WhatsApp replace the ack reaction during a turn instead of leaving a static receipt emoji, cycling through states such as queued, thinking, tool activity, compaction, done, and error:

{
  messages: {
    statusReactions: {
      enabled: true,
    },
  },
}

Notes: messages.ackReactionScope still controls eligibility for direct messages and groups; the queued state uses the same effective emoji as plain acknowledgment reactions. WhatsApp has one bot reaction slot per message, so lifecycle updates replace the current reaction in place and restore the acknowledgment after the final done/error state.

Active-turn typing

For admitted automatic turns where typing is allowed, WhatsApp sends a composing presence update when agent execution begins and refreshes it while the turn remains active. Refreshing stops when the run completes, including terminal failure or cancellation. The controller seals and cleans up when the reply dispatcher reports idle, or after a short safety timeout if that signal does not arrive. Turns for which the existing typing and suppression policy disables typing do not start this activity.

Typing presence is ephemeral, best-effort activity feedback. It is not a persisted message, delivery receipt, or guarantee that every WhatsApp client will display continuous activity; reconnects and client behavior can make the indicator disappear. Lifecycle status reactions remain the persistent-looking opt-in status surface described above.

Multi-account and credentials

Account selection and defaults

Account ids come from channels.whatsapp.accounts. Default account selection is default if present, otherwise the first configured account id (alphabetically sorted). Account ids are normalized internally for lookup.

Named accounts look up shared settings in this sequence: the account itself, then accounts.default, and finally the channel root. Both dmPolicy and groupPolicy follow this pattern: when a value is absent, it inherits from the next level, but an explicitly set value takes precedence. If no policy is defined, direct messages rely on pairing while group chats use allowlist. The default account's authDir, enabled, name, and selfChatMode remain private and are not exposed to named accounts.

Credential paths and legacy compatibility

  • Current authentication path: ~/.openclaw/credentials/whatsapp/<accountId>/creds.json (fallback option: creds.json.bak)
  • The older default authentication stored in ~/.openclaw/credentials/ is still acknowledged and converted for default-account operations

Logout behavior

Running openclaw channels logout --channel whatsapp [--account <id>] wipes out the WhatsApp authentication state tied to that specific account. If the gateway is accessible, the logout routine first halts the live listener for that account, ensuring the linked session no longer receives messages before the subsequent restart. Similarly, openclaw channels remove --channel whatsapp shuts down the live listener ahead of disabling or removing the account configuration.

For legacy authentication directories, oauth.json stays intact while the Baileys authentication files get deleted.

Tools, actions, and config writes

  • Agent tooling supports the WhatsApp reaction action (react).
  • Action gating rules: channels.whatsapp.actions.reactions, channels.whatsapp.actions.polls (with existing actions defaulting to true), and channels.whatsapp.actions.calls (defaulting to false, as described in the MeowCaller section above).
  • Channel-triggered configuration updates are active by default; turn them off using channels.whatsapp.configWrites: false.

Troubleshooting

Not linked (QR required)

Observed issue: the channel status shows no linked connection.

openclaw channels login --channel whatsapp
openclaw channels status

Linked but disconnected / reconnect loop

Observed issue: a linked account repeatedly drops its connection or keeps attempting to reconnect.

Quiet accounts can maintain their connection past the standard message timeout; the watchdog only triggers a restart when WhatsApp Web transport activity ceases, the socket closes, or application-level activity remains dormant beyond the extended safety threshold (refer to the Runtime model section above).

Resolution:

openclaw channels status --probe
openclaw doctor
openclaw logs --follow
openclaw gateway status

Should the loop continue after host connectivity and timing issues are addressed, back up the account's authentication directory and re-link:

cp -a ~/.openclaw/credentials/whatsapp/<accountId> \
  ~/.openclaw/credentials/whatsapp/<accountId>.bak
openclaw channels logout --channel whatsapp --account <accountId>
openclaw channels login --channel whatsapp --account <accountId>

If ~/.openclaw/logs/whatsapp-health.log reports Gateway inactive while openclaw gateway status and openclaw channels status --probe both indicate healthy status, execute openclaw doctor. On Linux, the doctor flags outdated crontab entries that invoke the deprecated ~/.openclaw/bin/ensure-whatsapp.sh script; clear those entries using crontab -e, since cron may lack the systemd user-bus environment, causing that old script to incorrectly report gateway health.

QR login times out behind a proxy

Observed issue: openclaw channels login --channel whatsapp fails before displaying a usable QR due to status=408 Request Time-out or a TLS socket disconnection.

WhatsApp Web login depends on the gateway host's standard proxy configuration (HTTPS_PROXY, HTTP_PROXY, their lowercase equivalents, and NO_PROXY). Ensure the gateway process inherits the proxy environment and that NO_PROXY does not equal mmg.whatsapp.net.

No active listener when sending

Outbound sends fail immediately when no active gateway listener exists for the target account. Verify the gateway is operational and the account is linked.

Reply appears in transcript but not in WhatsApp

Transcript rows capture what the agent produced; WhatsApp delivery is verified independently. OpenClaw considers an auto-reply as sent only after Baileys returns an outbound message id for at least one visible text or media transmission.

Ack reactions serve as separate pre-reply confirmations; a successful reaction does not guarantee that the subsequent text or media reply was accepted. Inspect gateway logs for auto-reply delivery failed or auto-reply was not accepted by WhatsApp provider.

Group messages unexpectedly ignored

Investigate in this sequence: groupPolicy, groupAllowFrom/allowFrom, the groups allowlist entries, mention gating (requireMention combined with mention patterns), and duplicate keys in openclaw.json (in JSON5, later entries override earlier ones, so keep a single groupPolicy per scope).

If channels.whatsapp.groups is set, WhatsApp remains able to see messages from other groups, yet OpenClaw discards them prior to session routing. Either append the group JID to channels.whatsapp.groups, or include groups["*"] to permit all groups while sender authorization continues to be governed by groupPolicy/groupAllowFrom.

Bun runtime warning

Node continues to be the primary and recommended runtime for Gateway. Bun 1.4+ builds that include WAL-reset-safe node:sqlite are explicitly supported as an opt-in; doctor only migrates unsupported Bun services over to Node.

System prompts

For both group and direct chats, WhatsApp supports Telegram-style system prompts through the groups and direct maps.

Resolution for group messages: the effective groups map is established first. If the account defines its own groups key at all, that map fully replaces the root groups map, with no deep merging. Prompt lookup then operates on that single resulting map:

  1. Group-specific prompt (groups["<groupId>"].systemPrompt): applied when the group entry is present and its systemPrompt key is defined. An empty string ("") suppresses the wildcard and applies no prompt.
  2. Group wildcard prompt (groups["*"].systemPrompt): applied when the specific group entry is missing, or exists without a systemPrompt key.

Direct message resolution follows the same pattern against the direct map and direct["*"].

Note

dms remains the lightweight per-DM history override bucket (dms.<id>.historyLimit). Prompt overrides are located under direct.

Note

This account-replaces-root behavior for prompt resolution is a plain shallow override: any account groups/direct key, including an explicit empty object, replaces the root map. This differs from the group-membership allowlist check described above, which includes a single-account safety net for an accidentally empty groups: {}.

Difference from Telegram: Telegram applies the same whole-map account override for groups in multi-account configurations, but a single account's empty groups: {} falls back to root groups as a migration safety net. Telegram's direct map also carries separate DM-topic semantics. In WhatsApp, or for one account among several Telegram accounts, use an explicit empty groups: {} when that account should not inherit root group defaults.

Important behavior:

  • channels.whatsapp.groups serves both as a per-group config map and as the chat-level group allowlist. At either root or account scope, groups["*"] means "all groups are admitted" for that scope.
  • Add a wildcard systemPrompt only when you already want that scope to admit all groups. To keep only a fixed set of group IDs eligible, repeat the prompt on each explicitly allowlisted entry instead of using groups["*"].
  • Group admission and sender authorization are separate checks. groups["*"] widens which groups reach group handling; it does not authorize every sender in those groups, which remains controlled by groupPolicy/groupAllowFrom.
  • channels.whatsapp.direct has no equivalent side effect for DMs: direct["*"] only supplies a default config after a DM is already admitted by dmPolicy plus allowFrom or pairing-store rules.

Example:

{
  channels: {
    whatsapp: {
      groups: {
        // Use only if all groups should be admitted at the root scope.
        // Applies to all accounts that do not define their own groups map.
        "*": { systemPrompt: "Default prompt for all groups." },
      },
      direct: {
        // Applies to all accounts that do not define their own direct map.
        "*": { systemPrompt: "Default prompt for all direct chats." },
      },
      accounts: {
        work: {
          groups: {
            // This account defines its own groups, so root groups are fully
            // replaced. To keep a wildcard, define "*" explicitly here too.
            "120363406415684625@g.us": {
              requireMention: false,
              systemPrompt: "Focus on project management.",
            },
            // Use only if all groups should be admitted in this account.
            "*": { systemPrompt: "Default prompt for work groups." },
          },
          direct: {
            // This account defines its own direct map, so root direct entries are
            // fully replaced. To keep a wildcard, define "*" explicitly here too.
            "+15551234567": { systemPrompt: "Prompt for a specific work direct chat." },
            "*": { systemPrompt: "Default prompt for work direct chats." },
          },
        },
      },
    },
  },
}

Configuration reference pointers

Primary reference: Configuration reference - WhatsApp

AreaFields
AccessdmPolicy, allowFrom, groupPolicy, groupAllowFrom, groups
DeliverytextChunkLimit, streaming.chunkMode, mediaMaxMb, sendReadReceipts, reactionLevel
Multi-accountaccounts.<id>.enabled, accounts.<id>.authDir, and other per-account overrides
OperationsconfigWrites, enabled
Inbound batchingmessages.inbound.debounceMs, messages.inbound.byChannel.whatsapp
Acknowledgmentsmessages.ackReaction, messages.ackReactionScope
Session behaviorsession.dmScope, historyLimit, dmHistoryLimit, dms.<id>.historyLimit
Promptsgroups.<id>.systemPrompt, groups["*"].systemPrompt, direct.<id>.systemPrompt, direct["*"].systemPrompt
4,574 words ยท updated Sep 1, 2026