OpenClawSkills
GitHub
Channels β€’ TutorialHeader.readTime

WhatsApp

WhatsApp (web channel) integration: login, inbox, replies, media, and ops.

Status: Only supports WhatsApp Web via Baileys. Sessions are managed by Gateway.

Tutorial.step

Quick Start for Beginners

1. Use a separate phone number if possible (recommended).

2. Configure WhatsApp in ~/.openclaw/openclaw.json.

3. Run openclaw channels login to scan QR code (WhatsApp β†’ Settings β†’ Linked Devices).

4. Start Gateway.

Minimum configuration example:

Json5
{
  channels: {
    whatsapp: {
      dmPolicy: "allowlist",
      allowFrom: ["+15551234567"],
    },
  },
}
Tutorial.step

Goals

- Support multiple WhatsApp accounts in the same Gateway process (multi-account).

- Deterministic routing: messages from WhatsApp go back to WhatsApp (don't let model choose channel).

- Model can see enough quote/reply context to understand "which message I'm replying to".

Tutorial.step

Config Writes

By default, WhatsApp allows config updates triggered by /config set|unset to be written back to config file (requires commands.config: true).

Disable:

Json5
{
  channels: { whatsapp: { configWrites: false } },
}
Tutorial.step

Architecture (Who Does What)

- Gateway handles Baileys socket and inbox loop.

- CLI / macOS app only communicates with Gateway, doesn't use Baileys directly.

- Outbound sending requires active listener; otherwise it fails fast (no web session).

Tutorial.step

Getting a Phone Number (Two Modes)

WhatsApp requires a real phone number for verification. VoIP/virtual numbers are usually blocked. OpenClaw has two recommended ways to run on WhatsApp:

#

Tutorial.step

Dedicated Number (Recommended)

Give OpenClaw a separate number. Best experience: clear routing, no weird edge cases of "messaging yourself". Ideal setup: spare/old Android phone + eSIM, connected to Wi-Fi and power, linked via QR code.

WhatsApp Business: You can install WhatsApp and WhatsApp Business with different numbers on the same device. Putting OpenClaw in Business is a good isolation.

Example config (dedicated number, single-user allowlist):

Json5
{
  channels: {
    whatsapp: {
      dmPolicy: "allowlist",
      allowFrom: ["+15551234567"],
    },
  },
}

Optional: Pairing Mode

If you want to use pairing instead of allowlist, set channels.whatsapp.dmPolicy to pairing. Unknown senders will receive a pairing code; approve with:

openclaw pairing approve whatsapp <code>

#

Tutorial.step

Personal Number (Fallback)

Fallback: let OpenClaw run on your own number. For testing, you can message yourself in WhatsApp's "Message yourself" to avoid bothering contacts. You'll need to read verification code on your main phone during setup. Must enable self-chat mode.

When wizard asks for your personal WhatsApp number, enter the number "you'll send messages to assistant from" (owner/sender), not "assistant number" (since it's the same number here).

Example config (personal number + self-chat):

Json
{
  "whatsapp": {
    "selfChatMode": true,
    "dmPolicy": "allowlist",
    "allowFrom": ["+15551234567"]
  }
}

In self-chat mode, if messages.responsePrefix is not set, reply prefix defaults to [{identity.name}] (otherwise [openclaw]). To customize or disable prefix, set explicitly (use "" to remove).

#

Tutorial.step

Number Source Recommendations

- Local eSIM from carrier in your country (most stable)

- Austria: ''hot.at''

- UK: ''giffgaff'' (free SIM, no contract)

- Prepaid SIM β€” just needs to receive one verification SMS

Avoid: TextNow, Google Voice, most "free SMS receive" services (WhatsApp blocks aggressively).

Tip: Number only needs to receive one verification SMS. After that, WhatsApp Web session persists via creds.json.

Tutorial.step

Why Not Twilio?

- Early OpenClaw supported Twilio's WhatsApp Business integration.

- WhatsApp Business numbers aren't suitable for personal assistants.

- Meta enforces 24-hour reply window; Business numbers can't initiate new messages after 24 hours of inactivity.

- High-frequency/chatty use triggers more aggressive blocking because Business accounts aren't meant for sending many personal assistant messages.

- Result: unstable delivery, frequent blocking, so support was removed.

Tutorial.step

Login and Credentials

- Login command: openclaw channels login (scan QR code: Linked Devices).

- Multi-account login: openclaw channels login --account <id> (<id> = accountId).

- Default account: when --account is omitted, use default if exists, otherwise first configured account id by sort order.

- Credentials storage: ~/.openclaw/credentials/whatsapp/<accountId>/creds.json.

- Backup copy: creds.json.bak (used for recovery if corrupted).

- Legacy compatibility: older installs put Baileys files directly under ~/.openclaw/credentials/.

- Logout: openclaw channels logout (or --account <id>) deletes WhatsApp auth state (but keeps shared oauth.json).

- Unlogged sockets will error and prompt to relink.

Tutorial.step

Inbound Flow (DM + Groups)

- WhatsApp events come from Baileys' messages.upsert.

- To avoid accumulating event handlers during testing/restarts, inbox listeners are unmounted on shutdown.

- Ignores status/broadcast chats.

- DMs use E.164; groups use group JID.

- DM Policy: channels.whatsapp.dmPolicy controls DM access (default pairing).

- pairing: unknown senders receive pairing code (openclaw pairing approve whatsapp <code>; expires in 1 hour).

- open: requires channels.whatsapp.allowFrom to contain "*".

- Your bound WhatsApp number is implicitly trusted: self-sent messages skip dmPolicy and allowFrom checks.

#

Tutorial.step

Personal Number Mode (Fallback)

If you run OpenClaw on your personal WhatsApp number, enable channels.whatsapp.selfChatMode (see example above).

Behavior:

- Outbound DMs won't trigger pairing replies (avoid spamming contacts).

- Inbound unknown senders still follow channels.whatsapp.dmPolicy.

- self-chat mode (allowFrom contains your own number) avoids automatic read receipts and ignores mention JIDs.

- Non-self-chat DMs send read receipts.

Tutorial.step

Read Receipts

By default, Gateway marks WhatsApp inbound messages as read (blue checkmarks) after they're accepted.

Global disable:

Json5
{
  channels: { whatsapp: { sendReadReceipts: false } },
}

Per-account disable:

Json5
{
  channels: {
    whatsapp: {
      accounts: {
        personal: { sendReadReceipts: false },
      },
    },
  },
}

Notes:

- self-chat mode always skips read receipts.

Tutorial.step

WhatsApp FAQ: Messaging and Pairing

Will OpenClaw send messages to random contacts after linking WhatsApp?

No. Default DM policy is pairing: unknown senders only receive a pairing code, their messages won't be processed. OpenClaw only replies to chats it receives, or sends you explicitly trigger (agent/CLI).

How does WhatsApp pairing work?

Pairing is a DM gatekeeper for unknown senders:

- New sender's first DM receives a short code (message not processed).

- Approve: openclaw pairing approve whatsapp <code> (list: openclaw pairing list whatsapp).

- Pairing code expires in 1 hour; default pending request limit per channel is 3.

Can one WhatsApp number be used by multiple people with different OpenClaw instances?

Yes: route different senders to different agents via ''bindings'' (peer ''kind: "dm"'', sender with ''+1555...'' E.164). But replies still come from ''same WhatsApp account'', and DMs fold into each agent's main session, so recommend ''one agent per person''. DM access control (''dmPolicy''/''allowFrom'') is global per WhatsApp account. See ''Multi-Agent Routing''.

Why does wizard ask for my phone number?

Wizard uses it to set your owner/allowlist, ensuring your own DMs are allowed. It won't be used for auto-sending. If you run on personal number, enter same number and enable channels.whatsapp.selfChatMode.

Tutorial.step

Message Normalization (What Model Sees)

- Body is current message body (with envelope).

- Quote/reply context is always appended:

[Replying to +1555 id:ABC123]
&lt;quoted text or &lt;media:...&gt;&gt;
[/Replying]

- Reply metadata is also set:

- ReplyToId = stanzaId

- ReplyToBody = quoted body or media placeholder

- ReplyToSender = E.164 when available

- Pure media inbound messages use placeholder:

- <media:image|video|audio|document|sticker>

Tutorial.step

Groups

- Group session key: agent:<agentId>:whatsapp:group:<jid>.

- Group policy: channels.whatsapp.groupPolicy = open|disabled|allowlist (default allowlist).

- Trigger modes:

- mention (default): requires @mention or regex match.

- always: always trigger.

- /activation mention|always only available to owner, must be sent as separate message.

- owner = channels.whatsapp.allowFrom (or self E.164 if not set).

- History Injection (pending only):

- Recent unprocessed messages (default 50) inserted into:

[Chat messages since your last reply - for context]

- Current message inserted into:

[Current message - respond to this]

- Sender info appended at end: [from: Name (+E164)]

- Group metadata cached for 5 minutes (subject + members).

Tutorial.step

Reply Delivery (Threads)

- Current gateway's WhatsApp Web outbound only sends plain messages (no quoted reply threading).

- Reply tags are ignored on this channel.

Tutorial.step

Ack Reaction (Auto-react on Receive)

WhatsApp can automatically send an emoji reaction immediately after receiving a message (before bot generates reply), letting user know "message received" right away.

Configuration:

Json
{
  "whatsapp": {
    "ackReaction": {
      "emoji": "πŸ‘€",
      "direct": true,
      "group": "mentions"
    }
  }
}

Options:

- emoji (string): Emoji for acknowledgment (e.g., "πŸ‘€", "βœ…", "πŸ“¨"). Empty or omitted means disabled.

- direct (boolean): Enable for DMs (default: true).

- group (string|boolean): Enable for groups. "mentions" = only when mentioned; true = always; false = disabled (default: "mentions").