WhatsApp (Archive)
Archived original-language source from the legacy CrabClaw docs. This page is intentionally not machine-translated.
状态:WhatsApp Web 集成。Go Gateway 拥有会话。
Quick setup (beginner)
- Use a separate phone number if possible (recommended).
- Configure WhatsApp in
~/.crabclaw/crabclaw.json. - Run
crabclaw channels loginto scan the QR code (Linked Devices). - Start the gateway.
Minimal config:
{
channels: {
whatsapp: {
dmPolicy: "allowlist",
allowFrom: ["+15551234567"],
},
},
}{
channels: {
whatsapp: {
dmPolicy: "allowlist",
allowFrom: ["+15551234567"],
},
},
}Goals
- Multiple WhatsApp accounts (multi-account) in one Gateway process.
- Deterministic routing: replies return to WhatsApp, no model routing.
- Model sees enough context to understand quoted replies.
Config writes
By default, WhatsApp is allowed to write config updates triggered by /config set|unset (requires commands.config: true).
Disable with:
{
channels: { whatsapp: { configWrites: false } },
}{
channels: { whatsapp: { configWrites: false } },
}架构(职责划分)
- Go Gateway 拥有 WhatsApp 连接和收件箱循环(
backend/internal/channels/whatsapp/)。 - Rust CLI / macOS 应用 与 Gateway 通信;不直接操作 WhatsApp 连接。
- 活跃监听器 是出站发送的前提;否则发送快速失败。
Getting a phone number (two modes)
WhatsApp requires a real mobile number for verification. VoIP and virtual numbers are usually blocked. There are two supported ways to run Crab Claw(蟹爪) on WhatsApp:
Dedicated number (recommended)
Use a separate phone number for Crab Claw(蟹爪). Best UX, clean routing, no self-chat quirks. Ideal setup: spare/old Android phone + eSIM. Leave it on Wi‑Fi and power, and link it via QR.
WhatsApp Business: You can use WhatsApp Business on the same device with a different number. Great for keeping your personal WhatsApp separate — install WhatsApp Business and register the Crab Claw(蟹爪) number there.
Sample config (dedicated number, single-user allowlist):
{
channels: {
whatsapp: {
dmPolicy: "allowlist",
allowFrom: ["+15551234567"],
},
},
}{
channels: {
whatsapp: {
dmPolicy: "allowlist",
allowFrom: ["+15551234567"],
},
},
}Pairing mode (optional):
If you want pairing instead of allowlist, set channels.whatsapp.dmPolicy to pairing. Unknown senders get a pairing code; approve with:
crabclaw pairing approve whatsapp <code>
Personal number (fallback)
Quick fallback: run Crab Claw(蟹爪) on your own number. Message yourself (WhatsApp “Message yourself”) for testing so you don’t spam contacts. Expect to read verification codes on your main phone during setup and experiments. Must enable self-chat mode. When the wizard asks for your personal WhatsApp number, enter the phone you will message from (the owner/sender), not the assistant number.
Sample config (personal number, self-chat):
{
"whatsapp": {
"selfChatMode": true,
"dmPolicy": "allowlist",
"allowFrom": ["+15551234567"]
}
}{
"whatsapp": {
"selfChatMode": true,
"dmPolicy": "allowlist",
"allowFrom": ["+15551234567"]
}
}Self-chat replies default to [{identity.name}] when set (otherwise [crabclaw])
if messages.responsePrefix is unset. Set it explicitly to customize or disable
the prefix (use "" to remove it).
Number sourcing tips
- Local eSIM from your country's mobile carrier (most reliable)
- Prepaid SIM — cheap, just needs to receive one SMS for verification
Avoid: TextNow, Google Voice, most "free SMS" services — WhatsApp blocks these aggressively.
Tip: The number only needs to receive one verification SMS. After that, WhatsApp Web sessions persist via creds.json.
Why Not Twilio?
- Earlier project builds supported Twilio’s WhatsApp Business integration.
- WhatsApp Business numbers are a poor fit for a personal assistant.
- Meta enforces a 24‑hour reply window; if you haven’t responded in the last 24 hours, the business number can’t initiate new messages.
- High-volume or “chatty” usage triggers aggressive blocking, because business accounts aren’t meant to send dozens of personal assistant messages.
- Result: unreliable delivery and frequent blocks, so support was removed.
Login + credentials
- Login command:
crabclaw channels login(QR via Linked Devices). - Multi-account login:
crabclaw channels login --account <id>(<id>=accountId). - Default account (when
--accountis omitted):defaultif present, otherwise the first configured account id (sorted). - Credentials stored in
~/.crabclaw/credentials/whatsapp/<accountId>/creds.json. - Backup copy at
creds.json.bak(restored on corruption). - Legacy compatibility: older installs stored Baileys files directly in
~/.crabclaw/credentials/. - Logout:
crabclaw channels logout(or--account <id>) deletes WhatsApp auth state (but keeps sharedoauth.json). - Logged-out socket => error instructs re-link.
Inbound flow (DM + group)
- WhatsApp 事件来自 Go Gateway 的消息接收循环。
- 收件箱监听器在关闭时分离以避免在测试/重启中累积事件处理器。
- Status/broadcast chats are ignored.
- Direct chats use E.164; groups use group JID.
- DM policy:
channels.whatsapp.dmPolicycontrols direct chat access (default:pairing).- Pairing: unknown senders get a pairing code (approve via
crabclaw pairing approve whatsapp <code>; codes expire after 1 hour). - Open: requires
channels.whatsapp.allowFromto include"*". - Your linked WhatsApp number is implicitly trusted, so self messages skip
channels.whatsapp.dmPolicyandchannels.whatsapp.allowFromchecks.
- Pairing: unknown senders get a pairing code (approve via
Personal-number mode (fallback)
If you run Crab Claw(蟹爪) on your personal WhatsApp number, enable channels.whatsapp.selfChatMode (see sample above).
Behavior:
- Outbound DMs never trigger pairing replies (prevents spamming contacts).
- Inbound unknown senders still follow
channels.whatsapp.dmPolicy. - Self-chat mode (allowFrom includes your number) avoids auto read receipts and ignores mention JIDs.
- Read receipts sent for non-self-chat DMs.
Read receipts
By default, the gateway marks inbound WhatsApp messages as read (blue ticks) once they are accepted.
Disable globally:
{
channels: { whatsapp: { sendReadReceipts: false } },
}{
channels: { whatsapp: { sendReadReceipts: false } },
}Disable per account:
{
channels: {
whatsapp: {
accounts: {
personal: { sendReadReceipts: false },
},
},
},
}{
channels: {
whatsapp: {
accounts: {
personal: { sendReadReceipts: false },
},
},
},
}Notes:
- Self-chat mode always skips read receipts.
WhatsApp FAQ: sending messages + pairing
Will Crab Claw(蟹爪) message random contacts when I link WhatsApp? No. Default DM policy is pairing, so unknown senders only get a pairing code and their message is not processed. Crab Claw(蟹爪) only replies to chats it receives, or to sends you explicitly trigger (agent/CLI).
How does pairing work on WhatsApp? Pairing is a DM gate for unknown senders:
- First DM from a new sender returns a short code (message is not processed).
- Approve with:
crabclaw pairing approve whatsapp <code>(list withcrabclaw pairing list whatsapp). - Codes expire after 1 hour; pending requests are capped at 3 per channel.
Can multiple people use different Crab Claw(蟹爪) instances on one WhatsApp number?
Yes, by routing each sender to a different agent via bindings (peer kind: "direct", sender E.164 like +15551234567). Replies still come from the same WhatsApp account, and direct chats collapse to each agent's main session, so use one agent per person. DM access control (dmPolicy/allowFrom) is global per WhatsApp account. See Multi-Agent Routing.
Why do you ask for my phone number in the wizard?
The wizard uses it to set your allowlist/owner so your own DMs are permitted. It’s not used for auto-sending. If you run on your personal WhatsApp number, use that same number and enable channels.whatsapp.selfChatMode.
Message normalization (what the model sees)
-
Bodyis the current message body with envelope. -
Quoted reply context is always appended:
[Replying to +1555 id:ABC123] <quoted text or <media:...>> [/Replying][Replying to +1555 id:ABC123] <quoted text or <media:...>> [/Replying] -
Reply metadata also set:
ReplyToId= stanzaIdReplyToBody= quoted body or media placeholderReplyToSender= E.164 when known
-
Media-only inbound messages use placeholders:
<media:image|video|audio|document|sticker>
Groups
- Groups map to
agent:<agentId>:whatsapp:group:<jid>sessions. - Group policy:
channels.whatsapp.groupPolicy = open|disabled|allowlist(defaultallowlist). - Activation modes:
mention(default): requires @mention or regex match.always: always triggers.
/activation mention|alwaysis owner-only and must be sent as a standalone message.- Owner =
channels.whatsapp.allowFrom(or self E.164 if unset). - History injection (pending-only):
- Recent unprocessed messages (default 50) inserted under:
[Chat messages since your last reply - for context](messages already in the session are not re-injected) - Current message under:
[Current message - respond to this] - Sender suffix appended:
[from: Name (+E164)]
- Recent unprocessed messages (default 50) inserted under:
- Group metadata cached 5 min (subject + participants).
Reply delivery (threading)
- WhatsApp Web sends standard messages (no quoted reply threading in the current gateway).
- Reply tags are ignored on this channel.
Acknowledgment reactions (auto-react on receipt)
WhatsApp can automatically send emoji reactions to incoming messages immediately upon receipt, before the bot generates a reply. This provides instant feedback to users that their message was received.
Configuration:
{
"whatsapp": {
"ackReaction": {
"emoji": "👀",
"direct": true,
"group": "mentions"
}
}
}{
"whatsapp": {
"ackReaction": {
"emoji": "👀",
"direct": true,
"group": "mentions"
}
}
}Options:
emoji(string): Emoji to use for acknowledgment (e.g., "👀", "✅", "📨"). Empty or omitted = feature disabled.direct(boolean, default:true): Send reactions in direct/DM chats.group(string, default:"mentions"): Group chat behavior:"always": React to all group messages (even without @mention)"mentions": React only when bot is @mentioned"never": Never react in groups
Per-account override:
{
"whatsapp": {
"accounts": {
"work": {
"ackReaction": {
"emoji": "✅",
"direct": false,
"group": "always"
}
}
}
}
}{
"whatsapp": {
"accounts": {
"work": {
"ackReaction": {
"emoji": "✅",
"direct": false,
"group": "always"
}
}
}
}
}Behavior notes:
- Reactions are sent immediately upon message receipt, before typing indicators or bot replies.
- In groups with
requireMention: false(activation: always),group: "mentions"will react to all messages (not just @mentions). - Fire-and-forget: reaction failures are logged but don't prevent the bot from replying.
- Participant JID is automatically included for group reactions.
- WhatsApp ignores
messages.ackReaction; usechannels.whatsapp.ackReactioninstead.
Agent tool (reactions)
- Tool:
whatsappwithreactaction (chatJid,messageId,emoji, optionalremove). - Optional:
participant(group sender),fromMe(reacting to your own message),accountId(multi-account). - Reaction removal semantics: see /tools/reactions.
- Tool gating:
channels.whatsapp.actions.reactions(default: enabled).
Limits
- Outbound text is chunked to
channels.whatsapp.textChunkLimit(default 4000). - Optional newline chunking: set
channels.whatsapp.chunkMode="newline"to split on blank lines (paragraph boundaries) before length chunking. - Inbound media saves are capped by
channels.whatsapp.mediaMaxMb(default 50 MB). - Outbound media items are capped by
agents.defaults.mediaMaxMb(default 5 MB).
Outbound send (text + media)
- Uses active web listener; error if gateway not running.
- Text chunking: 4k max per message (configurable via
channels.whatsapp.textChunkLimit, optionalchannels.whatsapp.chunkMode). - Media:
- Image/video/audio/document supported.
- Audio sent as PTT;
audio/ogg=>audio/ogg; codecs=opus. - Caption only on first media item.
- Media fetch supports HTTP(S) and local paths.
- Animated GIFs: WhatsApp expects MP4 with
gifPlayback: truefor inline looping.- CLI:
crabclaw message send --media <mp4> --gif-playback - Gateway:
sendparams includegifPlayback: true
- CLI:
Voice notes (PTT audio)
WhatsApp sends audio as voice notes (PTT bubble).
- Best results: OGG/Opus. Crab Claw(蟹爪) rewrites
audio/oggtoaudio/ogg; codecs=opus. [[audio_as_voice]]is ignored for WhatsApp (audio already ships as voice note).
Media limits + optimization
- Default outbound cap: 5 MB (per media item).
- Override:
agents.defaults.mediaMaxMb. - Images are auto-optimized to JPEG under cap (resize + quality sweep).
- Oversize media => error; media reply falls back to text warning.
Heartbeats
- Gateway heartbeat logs connection health (
web.heartbeatSeconds, default 60s). - Agent heartbeat can be configured per agent (
agents.list[].heartbeat) or globally viaagents.defaults.heartbeat(fallback when no per-agent entries are set).- Uses the configured heartbeat prompt (default:
Read HEARTBEAT.md if it exists (workspace context). Follow it strictly. Do not infer or repeat old tasks from prior chats. If nothing needs attention, reply HEARTBEAT_OK.) +HEARTBEAT_OKskip behavior. - Delivery defaults to the last used channel (or configured target).
- Uses the configured heartbeat prompt (default:
Reconnect behavior
- Backoff policy:
web.reconnect:initialMs,maxMs,factor,jitter,maxAttempts.
- If maxAttempts reached, web monitoring stops (degraded).
- Logged-out => stop and require re-link.
Config quick map
channels.whatsapp.dmPolicy(DM policy: pairing/allowlist/open/disabled).channels.whatsapp.selfChatMode(same-phone setup; bot uses your personal WhatsApp number).channels.whatsapp.allowFrom(DM allowlist). WhatsApp uses E.164 phone numbers (no usernames).channels.whatsapp.mediaMaxMb(inbound media save cap).channels.whatsapp.ackReaction(auto-reaction on message receipt:{emoji, direct, group}).channels.whatsapp.accounts.<accountId>.*(per-account settings + optionalauthDir).channels.whatsapp.accounts.<accountId>.mediaMaxMb(per-account inbound media cap).channels.whatsapp.accounts.<accountId>.ackReaction(per-account ack reaction override).channels.whatsapp.groupAllowFrom(group sender allowlist).channels.whatsapp.groupPolicy(group policy).channels.whatsapp.historyLimit/channels.whatsapp.accounts.<accountId>.historyLimit(group history context;0disables).channels.whatsapp.dmHistoryLimit(DM history limit in user turns). Per-user overrides:channels.whatsapp.dms["<phone>"].historyLimit.channels.whatsapp.groups(group allowlist + mention gating defaults; use"*"to allow all)channels.whatsapp.actions.reactions(gate WhatsApp tool reactions).agents.list[].groupChat.mentionPatterns(ormessages.groupChat.mentionPatterns)messages.groupChat.historyLimitchannels.whatsapp.messagePrefix(inbound prefix; per-account:channels.whatsapp.accounts.<accountId>.messagePrefix; deprecated:messages.messagePrefix)messages.responsePrefix(outbound prefix)agents.defaults.mediaMaxMbagents.defaults.heartbeat.everyagents.defaults.heartbeat.model(optional override)agents.defaults.heartbeat.targetagents.defaults.heartbeat.toagents.defaults.heartbeat.sessionagents.list[].heartbeat.*(per-agent overrides)session.*(scope, idle, store, mainKey)web.enabled(disable channel startup when false)web.heartbeatSecondsweb.reconnect.*
Logs + troubleshooting
- Subsystems:
whatsapp/inbound,whatsapp/outbound,web-heartbeat,web-reconnect. - Log file:
/tmp/crabclaw/crabclaw-YYYY-MM-DD.log(configurable). - Troubleshooting guide: Gateway troubleshooting.
Troubleshooting (quick)
Not linked / QR login required
- Symptom:
channels statusshowslinked: falseor warns “Not linked”. - Fix: run
crabclaw channels loginon the gateway host and scan the QR (WhatsApp → Settings → Linked Devices).
Linked but disconnected / reconnect loop
- Symptom:
channels statusshowsrunning, disconnectedor warns “Linked but disconnected”. - Fix:
crabclaw doctor(or restart the gateway). If it persists, relink viachannels loginand inspectcrabclaw logs --follow.
Go Gateway 运行时
- WhatsApp 频道已完全迁移至 Go Gateway,无需 Node.js 运行时。
- 使用
make gateway-dev或编译后的二进制文件运行。