安裝
openclaw onboard 和 openclaw channels add --channel whatsapp 會在你第一次選取此外掛時提示安裝;如果外掛缺失,openclaw channels login --channel whatsapp 也會提供相同的安裝流程。開發 checkout 使用本機外掛路徑;stable/beta 會先從 ClawHub 安裝 @openclaw/whatsapp,再 fallback 到 npm。WhatsApp runtime 會在核心 OpenClaw npm 套件之外發佈,因此其 runtime 依賴項會保留在外部外掛中。手動安裝:
@openclaw/whatsapp);只有在需要可重現安裝時才釘選精確版本。
配對
預設 DM policy 是讓未知寄件者進行配對。
頻道疑難排解
跨頻道診斷與修復 playbook。
閘道設定
完整頻道 config 模式與範例。
快速設定
連結 WhatsApp (QR)
建議使用獨立的 WhatsApp 號碼(設定與 metadata 已針對此情境最佳化),但也完整支援個人號碼/自我聊天設定。
部署模式
專用號碼(建議)
專用號碼(建議)
- 為 OpenClaw 使用獨立 WhatsApp 身分
- 更清楚的 DM allowlist 與路由邊界
- 較低的自我聊天混淆機率
個人號碼 fallback
個人號碼 fallback
Onboarding 支援個人號碼模式,並寫入適合自我聊天的 baseline:
dmPolicy: "allowlist"、包含你自己號碼的 allowFrom、selfChatMode: true。Runtime 自我聊天保護會根據已連結的自身號碼加上 allowFrom 判斷。Runtime 模型
- 閘道擁有 WhatsApp socket 與重新連線 loop。
- Watchdog 會分別追蹤兩個信號:原始 WhatsApp Web transport 活動,以及 application-message 活動。安靜但已連線的工作階段,不會只因為最近沒有收到訊息就重新啟動;只有在 transport frames 在固定內部時間窗(不可由使用者設定)停止抵達,或 application messages 靜默超過正常訊息 timeout 的 4 倍時,才會強制重新連線。對於最近仍活躍的工作階段,重新連線後的第一個時間窗會使用較短的正常訊息 timeout,而不是 4 倍時間窗。OpenClaw 可以自動回覆 Baileys 在該重新連線早期送達的 offline messages,範圍受 inbound message-ID dedupe lifetime 限制;初始啟動會保留較短的 stale-history guard。
- Baileys socket timing 明確位於
web.whatsapp.*:keepAliveIntervalMs(application ping 間隔)、connectTimeoutMs(opening handshake timeout)、defaultQueryTimeoutMs(Baileys query 等待,加上 OpenClaw 的 outbound send/presence 與 inbound read-receipt timeout)。 - Outbound sends 需要目標帳號有作用中的 WhatsApp listener;否則會快速失敗。
- Group sends 會為
@+<digits>和@<digits>tokens(文字與媒體 captions 中)附加原生 mention metadata,前提是 token 符合目前 participant metadata,包括 LID-backed groups。 - Status 與 broadcast chats (
@status,@broadcast) 會被忽略。 - Direct chats 使用 DM session 規則(
session.dmScope;預設main會將 DM 收斂到 agent main session)。Group sessions 會依 JID 隔離(agent:<agentId>:whatsapp:group:<jid>)。 - WhatsApp Channels/Newsletters 可以透過其原生
@newsletterJID 作為明確 outbound targets,使用 channel session metadata(agent:<agentId>:whatsapp:channel:<jid>),而不是 DM semantics。 - WhatsApp Web transport 會遵循閘道主機上的標準 proxy environment variables(
HTTPS_PROXY、HTTP_PROXY、NO_PROXY、小寫 variants)。優先使用 host-level proxy config,而不是 per-channel settings。 - 啟用
messages.removeAckAfterReply時,OpenClaw 會在可見回覆送達後清除 ack reaction。
使用 MeowCaller 撥打目前請求者(實驗性)
此外掛可以在 WhatsApp 來源的 agent turns 中公開whatsapp_call。它使用 MeowCaller 對目前已授權請求者撥打 WhatsApp voice call,並在對方接聽後播放 OpenClaw TTS 訊息。此工具沒有 destination-number 參數,因此 prompt 無法重新導向通話。預設停用。
啟用實驗性通話
將 缺少或為
actions.calls: true 加到 WhatsApp channel config,並重新啟動閘道:false 時,OpenClaw 不會公開 whatsapp_call 工具。安裝已審查的 MeowCaller 命令列介面
Adapter 預期閘道主機的 確保
PATH 上有 meowcaller 可執行檔。在 MeowCaller PR #7 合併前,請建置已審查分支:$HOME/.local/bin 位於閘道服務的 PATH 中。此 revision 有明確的 pair 與 send-only notify commands;notify 不會開啟 microphone、speaker、video device 或 diagnostic capture。請勿替換成 upstream example 命令列介面的 play command。配對 MeowCaller linked device
請 WhatsApp agent 檢查通話設定(互動式執行此命令,從 WhatsApp > 已連結裝置 掃描 QR,並等待
whatsapp_call status action 會回報 account-specific state directory 與 pairing command)。針對預設帳號:MeowCaller linked device ready。請將 wa-voip.db 保密,這是 MeowCaller session。非預設帳號會從 status action 取得自己的 store path;在 Windows 上,請執行其 PowerShell command。設定 TTS 並從 WhatsApp 撥打
設定支援電話語音的 TTS provider,重新啟動閘道,然後傳送請求,例如
Call me and say the build finished.。工具會從可信 inbound context 解析寄件者,合成暫時的私有 WAV 檔案,在有界通話時間窗內執行 MeowCaller,並在之後刪除音訊檔案。OpenClaw 會明確傳入該帳號的 store,等待接聽/播放/掛斷後的零 exit status,並將 timeout 或非零 exit 視為失敗的工具呼叫。核准 prompts
WhatsApp 可以將 exec 和外掛核准 prompts 顯示為👍/👎 reactions,由 top-level approval forwarding config 控制:
approvals.exec 和 approvals.plugin 彼此獨立;將 WhatsApp 啟用為頻道只會連結 transport,不會傳送任何內容,除非相符的 approval family 已啟用並路由到該處。Session mode 只會針對源自 WhatsApp 的核准傳送原生 emoji approvals。Target mode 會對明確 targets 使用共用 forwarding pipeline,不會建立獨立的 approver-DM fanout。
WhatsApp approval reactions 需要 allowFrom(或 "*")中有明確 approvers。defaultTo 設定一般預設訊息 targets,而不是 approver list。手動 /approve commands 在 approval resolution 前仍會通過正常的 WhatsApp sender-authorization path。
外掛 hooks 與隱私
Inbound WhatsApp messages 可能攜帶個人內容、電話號碼、group identifiers、sender names 與 session correlation fields。除非你 opt in,否則 WhatsApp 不會向外掛廣播 inboundmessage_received hook payloads:
channels.whatsapp.accounts.<id>.pluginHooks.messageReceived 下的單一帳號。只有在你信任外掛可處理 inbound WhatsApp content 與 identifiers 時才啟用此功能。
存取控制與啟用
- DM policy
- 群組政策與允許清單
- 提及與 /activation
channels.whatsapp.dmPolicy:| 值 | 行為 |
|---|---|
pairing(預設) | 未知寄件者請求配對;owner 核准 |
allowlist | 只允許 allowFrom 寄件者 |
open | 需要 allowFrom 包含 "*" |
disabled | 阻擋所有 DM |
allowFrom 接受 E.164-style numbers(內部會 normalize)。它只是 DM sender access-control list,不會限制明確 outbound sends 到 group JIDs 或 @newsletter channel JIDs。Multi-account override:channels.whatsapp.accounts.<id>.dmPolicy(以及 .allowFrom)會優先於該帳號的 channel-level defaults。Runtime notes:- 配對會保存在頻道 allow-store 中,並與設定的
allowFrom合併 - 排程自動化和心跳偵測收件者後援會使用明確的傳遞目標或設定的
allowFrom;DM 配對核准不會隱含成為排程/心跳偵測收件者 - 若未設定允許清單,預設允許已連結的本人號碼
- OpenClaw 永遠不會自動配對外送的
fromMeDM(你從已連結裝置傳給自己的訊息)
已設定的 ACP 繫結
WhatsApp 支援透過頂層bindings[] 使用持久 ACP 繫結:
個人號碼與自我聊天行為
當已連結的本人號碼也存在於allowFrom 時,自我聊天防護會啟用:略過自我聊天輪次的已讀回條、忽略可能 ping 到你自己的提及 JID 自動觸發行為,並在未設定 messages.responsePrefix 時,預設以 [{identity.name}](或 [openclaw])作為回覆前綴。
訊息正規化與情境
入站信封與回覆情境
入站信封與回覆情境
傳入訊息會包裝在共享入站信封中。引用回覆會以下列形式附加情境:可用時會填入回覆中繼資料(
ReplyToId、ReplyToBody、ReplyToSender、寄件者 JID/E.164)。如果引用目標是可下載媒體,OpenClaw 會透過一般入站媒體儲存區保存,並公開 MediaPath/MediaType,讓代理可以直接檢查,而不是只看到 <media:image>。媒體佔位符與位置/聯絡人擷取
媒體佔位符與位置/聯絡人擷取
純媒體訊息會正規化為佔位符:
<media:image>、<media:video>、<media:audio>、<media:document>、<media:sticker>。授權群組語音備忘錄在本文只有 <media:audio> 時,會在提及門檻之前先轉錄,因此在語音備忘錄中說出機器人提及即可觸發回覆。如果轉錄仍未提及機器人,則會保留在待處理群組歷史中,而不是原始佔位符。位置本文會呈現為簡短座標文字。位置標籤/註解和聯絡人/vCard 詳細資料會呈現為 fenced 不受信任的中繼資料,而不是內嵌提示文字。待處理群組歷史注入
待處理群組歷史注入
未處理的群組訊息會緩衝,並在機器人最終被觸發時注入為情境。
- 預設限制:
50 - 設定:
channels.whatsapp.historyLimit,後援為messages.groupChat.historyLimit 0會停用
[Chat messages since your last reply - for context] 和 [Current message - respond to this]。已讀回條
已讀回條
對接受的入站訊息預設啟用。全域停用:每個帳號覆寫:
channels.whatsapp.accounts.<id>.sendReadReceipts。即使全域啟用,自我聊天輪次也會略過已讀回條。傳遞、分塊與媒體
文字分塊
文字分塊
- 預設分塊限制:
channels.whatsapp.textChunkLimit = 4000 channels.whatsapp.chunkMode = "length" | "newline";newline會優先使用段落邊界(空行),然後退回使用長度安全的分塊
外送媒體行為
外送媒體行為
- 支援圖片、影片、音訊(PTT 語音備忘錄)和文件 payload
- 音訊會以 Baileys
audiopayload 傳送,並帶有ptt: true,呈現為按住說話語音備忘錄;回覆 payload 會保留audioAsVoice,因此無論提供者的來源格式為何,TTS 語音備忘錄輸出都會留在此路徑 - 原生 Ogg/Opus 音訊會以
audio/ogg; codecs=opus傳送;其他任何格式(包括 Microsoft Edge TTS MP3/WebM 輸出)都會在 PTT 傳遞前以ffmpeg轉碼為 48 kHz 單聲道 Ogg/Opus /tts latest會將最新助理回覆作為一則語音備忘錄傳送,並抑制同一回覆的重複傳送;/tts chat on|off|default控制目前聊天的自動 TTS- 影片傳送上的
gifPlayback: true會啟用動畫 GIF 播放 forceDocument/asDocument會透過 Baileys 文件 payload 路由外送圖片、GIF 和影片,以避免 WhatsApp 的媒體壓縮,並保留解析出的檔案名稱和 MIME 類型- 標題會套用到多媒體回覆中的第一個媒體項目,但 PTT 語音備忘錄除外:音訊會先傳送且沒有標題,接著標題會作為獨立文字訊息傳送(WhatsApp 用戶端不會一致呈現語音備忘錄標題)
- 媒體來源可以是 HTTP(S)、
file://或本機路徑
媒體大小限制與後援行為
媒體大小限制與後援行為
- 入站保存上限與外送傳送上限:
channels.whatsapp.mediaMaxMb(預設50) - 每個帳號覆寫:
channels.whatsapp.accounts.<id>.mediaMaxMb - 圖片會自動最佳化(調整大小/品質掃描)以符合限制,除非
forceDocument/asDocument要求文件傳遞 - 媒體傳送失敗時,第一項目後援會傳送文字警告,而不是靜默丟棄回應
回覆引用
channels.whatsapp.replyToMode 控制原生回覆引用(外送回覆會明顯引用入站訊息):
| 值 | 行為 |
|---|---|
"off"(預設) | 永不引用;作為純訊息傳送 |
"first" | 只引用第一個外送回覆分塊 |
"all" | 引用每個外送回覆分塊 |
"batched" | 引用已佇列的批次回覆;讓即時回覆不引用 |
channels.whatsapp.accounts.<id>.replyToMode。
回應層級
channels.whatsapp.reactionLevel 控制代理使用 emoji 回應的廣泛程度:
| 層級 | Ack 回應 | 代理發起的回應 |
|---|---|---|
"off" | 否 | 否 |
"ack" | 是 | 否 |
"minimal"(預設) | 是 | 是,保守指引 |
"extensive" | 是 | 是,鼓勵指引 |
channels.whatsapp.accounts.<id>.reactionLevel。
確認回應
channels.whatsapp.ackReaction 會在收到入站時立即傳送回應,並受 reactionLevel 門檻限制("off" 時抑制):
ackReaction 存在但沒有 emoji,WhatsApp 會使用被路由代理的身分 emoji,並退回到 ”👀“(省略 ackReaction 或設定 emoji: "" 表示不確認);失敗會記錄但不會阻擋回覆傳遞;群組模式 mentions 只在提及觸發的輪次回應,而群組啟用 always 會略過該檢查;WhatsApp 只使用 channels.whatsapp.ackReaction(舊版 messages.ackReaction 不適用於此)。
生命週期狀態回應
設定messages.statusReactions.enabled: true,讓 WhatsApp 在輪次期間取代確認回應,而不是保留靜態收件 emoji,並在已佇列、思考中、工具活動、壓縮、完成和錯誤等狀態之間循環:
channels.whatsapp.ackReaction 仍控制直接訊息和群組的適用資格;已佇列狀態使用與純確認回應相同的有效 emoji;WhatsApp 每則訊息有一個機器人回應槽,因此生命週期更新會就地取代目前回應;messages.removeAckAfterReply: true 會在設定的完成/錯誤保留時間後清除最終狀態回應;工具 emoji 類別包括 tool、coding、web、deploy、build 和 concierge。
多帳號與認證
帳號選擇與預設值
帳號選擇與預設值
帳號 ID 來自
channels.whatsapp.accounts。預設帳號選擇是 default(若存在),否則是第一個設定的帳號 ID(按字母排序)。帳號 ID 會在內部正規化以供查詢。憑證路徑與舊版相容性
憑證路徑與舊版相容性
- 目前的驗證路徑:
~/.openclaw/credentials/whatsapp/<accountId>/creds.json(備份:creds.json.bak) - 仍會辨識/遷移
~/.openclaw/credentials/中的舊版預設驗證,用於預設帳戶流程
登出行為
登出行為
openclaw channels logout --channel whatsapp [--account <id>] 會清除該帳戶的 WhatsApp 驗證狀態。當閘道可連線時,登出會先停止該帳戶的即時監聽器,因此已連結的工作階段會在下次重新啟動前停止接收訊息。openclaw channels remove --channel whatsapp 也會在停用或刪除帳戶設定前停止即時監聽器。在舊版驗證目錄中,會保留 oauth.json,並移除 Baileys 驗證檔案。工具、動作與設定寫入
- 代理程式工具支援包含 WhatsApp reaction 動作 (
react)。 - 動作閘門:
channels.whatsapp.actions.reactions、channels.whatsapp.actions.polls(現有動作預設為true)、channels.whatsapp.actions.calls(預設為false,請參閱上方 MeowCaller)。 - 頻道發起的設定寫入預設為啟用;可透過
channels.whatsapp.configWrites: false停用。
疑難排解
未連結(需要 QR)
未連結(需要 QR)
症狀:頻道狀態回報未連結。
已連結但中斷連線/重新連線循環
已連結但中斷連線/重新連線循環
症狀:已連結的帳戶反覆中斷連線或嘗試重新連線。安靜的帳戶可在一般訊息逾時後仍保持連線;只有在 WhatsApp Web 傳輸活動停止、socket 關閉,或應用程式層級活動沉默超過較長的安全視窗時,監看程式才會重新啟動(請參閱上方執行階段模型)。如果日誌顯示反覆出現 修復:如果主機連線能力與時序修復後循環仍持續,請備份帳戶驗證目錄並重新連結:如果
status=408 Request Time-out Connection was lost,請在 web.whatsapp 下調整 Baileys socket 時序。先將 keepAliveIntervalMs 縮短到低於你網路的閒置逾時,並在慢速或不穩定連線上增加 connectTimeoutMs:~/.openclaw/logs/whatsapp-health.log 顯示 Gateway inactive,但 openclaw gateway status 和 openclaw channels status --probe 都顯示健康,請執行 openclaw doctor。在 Linux 上,doctor 會警告舊版 crontab 項目呼叫已退役的 ~/.openclaw/bin/ensure-whatsapp.sh 指令碼;請用 crontab -e 移除這些項目,因為 cron 可能缺少 systemd 使用者匯流排環境,導致該舊指令碼誤報閘道健康狀態。QR 登入在代理後方逾時
QR 登入在代理後方逾時
症狀:
openclaw channels login --channel whatsapp 在顯示可用 QR 前失敗,並出現 status=408 Request Time-out 或 TLS socket 中斷連線。WhatsApp Web 登入使用閘道主機的標準代理環境(HTTPS_PROXY、HTTP_PROXY、小寫變體、NO_PROXY)。確認閘道程序繼承代理環境變數,且 NO_PROXY 不會比對 mmg.whatsapp.net。傳送時沒有作用中的監聽器
傳送時沒有作用中的監聽器
當目標帳戶沒有作用中的閘道監聽器時,對外傳送會快速失敗。確認閘道正在執行,且帳戶已連結。
回覆出現在逐字稿中,但未出現在 WhatsApp
回覆出現在逐字稿中,但未出現在 WhatsApp
逐字稿列會記錄代理程式產生的內容;WhatsApp 傳遞會另行檢查。只有在 Baileys 針對至少一則可見文字或媒體傳送回傳對外訊息 id 後,OpenClaw 才會將自動回覆視為已傳送。Ack reactions 是獨立的回覆前收據;成功的 reaction 不代表稍後的文字/媒體回覆已被接受。請檢查閘道日誌中是否有
auto-reply delivery failed 或 auto-reply was not accepted by WhatsApp provider。群組訊息意外遭忽略
群組訊息意外遭忽略
請依序檢查:
groupPolicy、groupAllowFrom/allowFrom、groups 允許清單項目、提及閘門(requireMention + 提及模式),以及 openclaw.json 中的重複鍵(JSON5 後面的項目會覆寫前面的項目,請在每個範圍只保留一個 groupPolicy)。如果存在 channels.whatsapp.groups,WhatsApp 仍可觀察來自其他群組的訊息,但 OpenClaw 會在工作階段路由前捨棄它們。請將群組 JID 加入 channels.whatsapp.groups,或加入 groups["*"] 以允許所有群組,同時仍透過 groupPolicy/groupAllowFrom 保持寄件者授權控管。Bun 執行階段警告
Bun 執行階段警告
WhatsApp 閘道執行階段應使用節點。Bun 會被標記為不相容於穩定的 WhatsApp/Telegram 閘道操作。
系統提示
WhatsApp 支援透過groups 和 direct 對應表,為群組與直接聊天使用 Telegram 風格的系統提示。
群組訊息的解析方式:會先決定有效的 groups 對應表;如果帳戶本身定義了任何 groups 鍵,它會完整取代根層 groups 對應表(沒有深度合併)。接著會在該單一結果對應表上執行提示查找:
- 群組專屬提示(
groups["<groupId>"].systemPrompt):當群組項目存在且其systemPrompt鍵已定義時使用。空字串("")會抑制萬用字元,且不套用任何提示。 - 群組萬用提示(
groups["*"].systemPrompt):當特定群組項目不存在,或存在但沒有systemPrompt鍵時使用。
direct 對應表和 direct["*"] 使用相同模式。
dms 仍是輕量的每個 DM 歷史覆寫儲存桶(dms.<id>.historyLimit)。提示覆寫位於 direct 之下。這種用帳戶取代根層的提示解析行為,是單純的淺層覆寫:任何帳戶
groups/direct 鍵,包括明確的空物件,都會取代根層對應表。它不同於上方描述的群組成員資格允許清單檢查;後者對意外空白的 groups: {} 有單一帳戶安全網。groups(即使帳戶本身沒有 groups),以避免機器人接收不屬於它的群組訊息。WhatsApp 不會套用該防護;任何沒有自身覆寫的帳戶都會繼承根層 groups/direct,無論帳戶數量多少。在多帳戶 WhatsApp 設定中,如果你想要每個帳戶各自的提示,請在每個帳戶下明確定義完整對應表。
重要行為:
channels.whatsapp.groups同時是每個群組的設定對應表,以及聊天層級的群組允許清單。在根層或帳戶範圍,groups["*"]表示該範圍「允許所有群組」。- 只有在你已經想讓該範圍允許所有群組時,才加入萬用
systemPrompt。若要只讓固定的一組群組 ID 符合資格,請在每個明確允許清單項目上重複提示,而不是使用groups["*"]。 - 群組准入與寄件者授權是不同檢查。
groups["*"]會擴大哪些群組能進入群組處理;它不會授權這些群組中的每個寄件者,寄件者授權仍由groupPolicy/groupAllowFrom控制。 channels.whatsapp.direct對 DM 沒有同等副作用:direct["*"]只會在 DM 已由dmPolicy加上allowFrom或配對儲存規則准入後,提供預設設定。
設定參考指標
主要參考:設定參考 - WhatsApp| 區域 | 欄位 |
|---|---|
| 存取 | dmPolicy, allowFrom, groupPolicy, groupAllowFrom, groups |
| 傳遞 | textChunkLimit, chunkMode, mediaMaxMb, sendReadReceipts, ackReaction, reactionLevel |
| 多帳戶 | accounts.<id>.enabled, accounts.<id>.authDir, and other per-account overrides |
| 操作 | configWrites, debounceMs, web.enabled, web.heartbeatSeconds, web.reconnect.*, web.whatsapp.* |
| 工作階段行為 | session.dmScope, historyLimit, dmHistoryLimit, dms.<id>.historyLimit |
| 提示 | groups.<id>.systemPrompt, groups["*"].systemPrompt, direct.<id>.systemPrompt, direct["*"].systemPrompt |