WhatsApp Business Cloud API 设置
本页的 Python 依赖命令基于一份 PM 准备好的源码 checkout。依赖变更后,重新激活 checkout 并重启 Hermes。
Hermes 可以通过 Meta 官方的 WhatsApp Business Cloud API 接入 WhatsApp。这是生产级路径:无 Node.js 桥接子进程、无二维码、无封号风险。
代价是:
- 你需要一个 Meta Business 账号(不是个人 WhatsApp)。
- 机器人运行在一个专用业务电话号码上,不是你的个人号码。
- Hermes 网关需要一个公网 HTTPS URL,以便 Meta 通过 webhook 投递入站消息。
- 距用户上一条消息超过 24 小时的回复需要预先审批的模板(这是 Meta 的"客服窗口"规则,不是 Hermes 限制)。
如果这些约束不适合你的场景,Baileys 桥接集成 是替代方案——个人账号、无需公网 URL,但非官方、易封号。
- Cloud API(本指南)——跑真正的业务机器人,要稳定性,能接受 Meta 审核 + 模板文书
- Baileys 桥接——个人项目、快速演示、单用户设置,愿意冒机器人手机号账号的风险
快速上手
hermes whatsapp-cloud
向导会带你过每一个凭据,在你粘贴时逐个校验(抓住头号设置陷阱——把电话号码粘进 Phone Number ID 字段),并为需要在向导外完成的步骤(启动 cloudflared、配置 Meta 的 webhook 仪表盘)打印确切的后续指引。
本页其余部分是手动参考。
前置条件
- 一个 Meta Business 账号。在 business.facebook.com 创建。
- 一个启用了 WhatsApp 的 Meta 应用。见下文"创建 Meta 应用"。
- 一种把本地端口暴露到公网并带 HTTPS 的方式。推荐 Cloudflare Tunnel(`cloudflared)——免费、无需端口转发、无需域名。ngrok、你自己带反向代理 + TLS 的域名,或直接把网关绑到公网 IP 的 VPS 也行。
- 可选但推荐:
PATH上有 ffmpeg,这样出站语音消息渲染为原生 WhatsApp 语音气泡(绿色波形),而非 MP3 音频附件。缺失时 Hermes 优雅降级。
创建 Meta 应用
- 前往 developers.facebook.com/apps → Create App。
- 选择用例:"Connect with customers through WhatsApp" → Next。
- 选择或创建一个业务作品集。查看发布要求。确认 → Create app。
- 创建后你会落到 Customize use case → Connect on WhatsApp → Quickstart。点击 Start using the API——你现在在 API Setup 页。
- 确认已链接一个 WhatsApp Business Account(WABA)。如果你在第 3 步新建了作品集,会自动创建一个。在 API Setup 页核验。
你需要从仪表盘拿到这些值——向导按此顺序提示:
| 值 | 仪表盘位置 | 字段形态 | 说明 |
|---|---|---|---|
| Phone Number ID | App Dashboard → WhatsApp → API Setup → "From" 下拉下方 | 数字,15-17 位 | 不是电话号码本身。头号设置错误就是把真实电话号码粘到这里。 |
| Access Token | App Dashboard → WhatsApp → API Setup → "Generate access token" | 以 EAA 开头,100+ 字符 | 临时 token 24 小时有效——生产环境见下文"永久 token"。 |
| App Secret | App Dashboard → Settings → Basic → App secret 旁点 "Show" | 32 字符小写 hex | 用于校验入站 webhook 签名。没有它,入站投递以 503 拒绝。 |
| App ID(可选) | App Dashboard → Settings → Basic | 数字,15-16 位 | 发消息不要求,便于分析。 |
| WABA ID(可选) | App Dashboard → WhatsApp → API Setup → 顶部附近 | 数字,15+ 位 | 发消息不要求,便于分析。 |
永久 token(生产)
临时 access token 24 小时后过期,意味着今天生成的 token 明天就失效。生产部署请用系统用户永久 token:
- 前往 business.facebook.com/latest/settings → System users(左侧边栏)。
- Add → 名字(如
hermes-bot)→ 角色:Admin。 - 选中新用户 → Assign Assets:
- 选你的应用 → 在 Full control 下打开 Manage app。
- 选你的 WhatsApp 账号 → 在 Full control 下打开 Manage WhatsApp Business Accounts。
- 点 Assign assets。
- 用以下权限生成 token:
business_managementwhatsapp_business_messagingwhatsapp_business_management
- 设 token 过期:Never。
- 复制 token → 更新
~/.hermes/.env中的WHATSAPP_CLOUD_ACCESS_TOKEN→ 重启网关。
系统用户 token 不过期,除非你显式吊销。
把 Hermes 暴露到公网
Cloud API 通过 HTTPS POST 把入站消息投递到你的 webhook URL——这意味着 Hermes 网关必须能被 Meta 的服务器访问。三种常见方式:
Cloudflare Tunnel(推荐)
免费、无需端口转发,Windows / macOS / Linux 通用。作为独立进程与网关并行运行。
安装:
# Windows
winget install Cloudflare.cloudflared
# macOS
brew install cloudflared
# Linux
# 从 https://github.com/cloudflare/cloudflared/releases 下载二进制
运行快速隧道(无需 Cloudflare 账号——给你一个 https://<random>.trycloudflare.com URL):
cloudflared tunnel --url http://localhost:8090
记下打印出的 URL——这就是你要给 Meta 的。
免费快速隧道 URL 每次重启 cloudflared 都会变。要稳定 URL,用 cloudflared tunnel login 登录并创建命名隧道。免费 Cloudflare 账号有无限命名隧道——命名隧道工作流见 Cloudflare 文档。
ngrok
ngrok http 8090
免费档每次重启 URL 都不同。付费档给你稳定子域名。
你自己的域名 + 反向代理
如果你已有带 TLS 证书的服务器(Caddy、nginx 等),把一条路由指向 localhost:8090。这是生产最稳的选择,但需要现有基础设施。
在 Meta 侧配置 webhook
隧道跑起来后:
- 记下隧道打印的公网 URL——假设是
https://abc123.trycloudflare.com。 - 生成一个 Verify Token——向导用
secrets.token_urlsafe(32)替你做;手动配置时运行:python -c "import secrets; print(secrets.token_urlsafe(32))"把它存为
~/.hermes/.env中的WHATSAPP_CLOUD_VERIFY_TOKEN。 - 启动 Hermes 网关:
hermes gateway。 - 在 Meta App Dashboard → WhatsApp → Configuration(或按 UI 版本 Use cases → Customize → Configuration)→ Webhook 区段点 Edit。
- 填写:
- Callback URL:
https://abc123.trycloudflare.com/whatsapp/webhook - Verify Token:第 2 步的字符串(必须完全一致)
- Callback URL:
- 点 Verify and save。Meta 用 GET 请求访问你的 URL,网关回显 challenge,Meta 把 webhook 标记为已验证。
- 在 Webhook fields 下点 Manage → 订阅 messages 字段。这才是告诉 Meta 真正把入站消息投到你 webhook 的开关。
手动验证回路(从第三个终端):
TUNNEL="https://abc123.trycloudflare.com"
VERIFY="<your verify token>"
# 应打印 HTTP 200,正文 "hello"
curl -i "$TUNNEL/whatsapp/webhook?hub.mode=subscribe&hub.verify_token=$VERIFY&hub.challenge=hello"
# 健康端点——应显示 verify_token_configured: true 和 app_secret_configured: true
curl "$TUNNEL/health"
收件人白名单(Meta 侧)
在开发模式(你的应用过 App Review 之前),Meta 限制机器人能给哪些号码发消息:
- App Dashboard → WhatsApp → API Setup → To 下拉。
- 点 Manage phone number list。
- 添加你想发消息的号码(你的、团队的、友好测试者)。Meta 通过短信或 WhatsApp 给每个号码发一个 6 位验证码。
开发模式最多 5 个号码。进入 App Review 后移除此限制。
允许列表(Hermes 侧)
除 Meta 的收件人白名单外,Hermes 有自己的按平台允许列表,控制agent 处理哪些入站消息。加到 ~/.hermes/.env:
# 逗号分隔的电话号码,带国家码,无 '+' / 空格 / 短横线
WHATSAPP_CLOUD_ALLOWED_USERS=15551234567,15557654321
# 或允许所有人(仅在与 Meta 收件人白名单组合时安全)
# WHATSAPP_CLOUD_ALLOW_ALL_USERS=true
向导在第 6 步设置它。没有允许列表时,每条入站消息都被拒绝——这是有意为之,这样即使收件人白名单哪天松了,机器人也不会被随机号码调用。
打磨机器人的 WhatsApp 资料
WhatsApp 在聊天头部和联系人列表为你的机器人显示名字和头像。这些不能通过 Cloud API 设置——它们在 Meta 的 Business Manager 里。
机器人跑起来后,前往 business.facebook.com/wa/manage/phone-numbers,点你的电话号码,你会找到:
| 内容 | 位置 | 说明 |
|---|---|---|
| Display name | 电话号码页顶部 | 改名走 Meta 的名称审核流程(约 24–48 小时)。 |
| Profile picture | 电话号码页顶部 | 方形图,建议 ≥640×640px。立即更新。 |
| About / 描述 / 网站 / 邮箱 / 营业时间 / 类别 | "Edit profile" 按钮 | 用户点机器人名字时出现在信息面板。装饰性。 |
| Verified 徽章(绿色对勾) | Business Manager → Security Center → Start Verification | 需要 Meta 单独的企业认证流程。 |
hermes whatsapp-cloud 向导在设置末尾打印这些链接。这些对机器人工作都不是必需的——纯粹是机器人在用户面前的门面。
配置参考
所有设置都在 ~/.hermes/.env。必填值用粗体。
| 变量 | 默认值 | 说明 |
|---|---|---|
WHATSAPP_CLOUD_PHONE_NUMBER_ID | — | API Setup 中的 15-17 位 ID。不是电话号码。 |
WHATSAPP_CLOUD_ACCESS_TOKEN | — | Meta access token(以 EAA 开头)。临时 24h 或系统用户永久。 |
WHATSAPP_CLOUD_APP_SECRET | — | Settings → Basic 中的 32 字符 hex。没有它,入站以 503 拒绝。 |
WHATSAPP_CLOUD_VERIFY_TOKEN | — | GET 握手的共享密钥。向导自动生成。 |
WHATSAPP_CLOUD_ALLOWED_USERS | — | 逗号分隔、允许给机器人发消息的 wa_id。 |
WHATSAPP_CLOUD_ALLOW_ALL_USERS | false | 设为 true 绕过允许列表。 |
WHATSAPP_CLOUD_APP_ID | — | 可选,供未来分析集成。 |
WHATSAPP_CLOUD_WABA_ID | — | 可选,供未来分析集成。 |
WHATSAPP_CLOUD_WEBHOOK_HOST | 未设置(双栈:所有接口,IPv4+IPv6) | webhook 服务器绑定的接口。 |
WHATSAPP_CLOUD_WEBHOOK_PORT | 8090 | webhook 服务器绑定端口。必须与你隧道转发的端口一致。 |
WHATSAPP_CLOUD_WEBHOOK_PATH | /whatsapp/webhook | Meta POST 的 URL 路径。 |
WHATSAPP_CLOUD_API_VERSION | v20.0 | Meta Graph API 版本。仅当 Meta 文档推荐更新版本时才覆盖。 |
WHATSAPP_CLOUD_HOME_CHANNEL | — | 用作机器人主频道的 wa_id(cron 任务等用)。 |
你可以同时启用 Baileys(whatsapp)和 Cloud(whatsapp_cloud)适配器,指向不同号码。
功能
入站
- 文本消息——直传给 agent。
- 图片——自动下载并附加到 agent 输入。原生视觉模型(Claude、GPT-4o、Gemini 等)直接读图;非视觉模型收到自动生成的文字描述。
- 语音消息——自动下载为
.ogg,经你配置的 STT 提供商转写(本地 faster-whisper、OpenAI/Nous、Groq 等),再作为文本交给 agent。 - 文档——自动下载。小的、文本可读文件(
.txt、.md、.json、.py、.csv等,≤100KB)内联进 agent 输入,让它无需工具调用即可读。更大的文件本地缓存,供 agent 其他工具访问。 - 按钮点击——用户点机器人之前发的按钮时(澄清选择、命令审批、斜杠命令确认),点击直接路由到对应处理器。过期点击回退为普通文本输入。
- 回复上下文——用户回复某条旧消息时,agent 看到原文作为上下文。引用一张图片、语音、视频或文档(你或机器人发的,例如 cron 投递的图表)也会把该文件附到这一轮,因此引用图片下问"这是什么?"可行。Meta 的 webhook 只带被引用消息 id,因此从本地最近收发索引解析(每网关最近 1000 条);更早的引用不带附件到达。
出站
- 文本——markdown 自动转成 WhatsApp 风味语法(
**bold**→*bold*、~~strike~~→~strike~、标题 → 粗体、[link](url)→link (url))。长消息按 4096 字符一块拆分。 - 图片——agent 生成的图片和本地图片文件都支持,作为原生照片附件投递。
- 语音消息——TTS 输出经 ffmpeg 转成原生 WhatsApp 语音气泡(绿色波形)。没装 ffmpeg 时回退为 MP3 音频附件。见下文"语音消息"。
- 视频 / 文档——都支持,作为原生附件发送。
交互式 UX
当 agent 触发这些流程时,Hermes 用 WhatsApp 原生交互消息——可点的按钮,而非"回复编号"提示:
clarify工具——多选题渲染为快速回复按钮(1–3 项)或可点开的列表面板(4+ 项)。选"✏️ Other"让用户输入自由文本,agent 收到作为结果。- 危险命令审批——当 agent 的终端/代码执行碰到门控命令时,用户看到
✅ Approve/❌ Deny按钮,而不必打/approve或/deny。 - 斜杠命令确认——
/reload-mcp这类特权命令显示✅ Approve Once/🔒 Always/❌ Cancel按钮。
按钮渲染失败时(例如旧版 WhatsApp 客户端),所有交互提示优雅降级为纯文本。
已读回执与正在输入指示
Hermes 立即确认入站消息:
- 网关一收到你的消息,它就显示蓝色双勾。
- agent 准备回复时,你 WhatsApp 聊天里机器人名字显示 "typing…"。
- 机器人首条回复到达时,正在输入指示自动消失。
这让"机器人看到你的消息了"与"它还在生成回复"一目了然。
语音消息
WhatsApp 区分"语音消息"(绿色波形气泡)和普通音频文件附件。区别纯粹在编解码:语音消息必须是 audio/ogg、opus 编码。
Hermes TTS 产出 MP3。两条路径:
- PATH 上有 ffmpeg(推荐)——出站 TTS 被转换,作为正经语音消息到达。安装:
- Windows:
winget install Gyan.FFmpeg - macOS:
brew install ffmpeg - Linux:包管理器
- Windows:
- 没有 ffmpeg——出站 TTS 作为 MP3 音频附件到达。能正常播放,只是不像语音消息。网关日志会一次性警告,让你知道。
你可以通过健康端点检查网关是否找到 ffmpeg:
curl http://localhost:8090/health
# 找 "ffmpeg_present": true
已知限制
24 小时对话窗口
Meta 只允许在用户最后一条入站消息后 24 小时窗口内发自由格式消息。窗口之外,Meta API 只接受预先审批的消息模板。
实际含义:
- 被动聊天(用户私信 → 机器人 24h 内回 → 用户再回 → …)永久可用。覆盖 >95% 的正常机器人使用。
- 间隔 >24h 后投递到 WhatsApp 的 cron 任务会以 Graph 错误码
131047("Re-engagement message")失败。 - 耗时超过 24h 的长时
delegate_task异步结果同样失败。 - 把外部事件路由到 WhatsApp 的 webhook 订阅者在用户最近没私信机器人时失败。
Hermes 在系统提示中就这个窗口警告 agent,因此模型排程延迟消息时知道要提一句。
消息模板支持(窗口外发送的 workaround)尚未在 Hermes 实现。如果你需要,请开 issue——已计划,但在等明确的需求信号。
群聊
Cloud API 的群组支持有限(按 Meta 的能力层级门控)。Hermes 的 whatsapp_cloud 适配器在 v1 中目前只处理私信。需要群聊请用 Baileys 桥接。
出站限流
Meta 默认吞吐是每个业务号码 80 条消息/秒,可升级。Hermes 目前不在客户端强制——极高量发送可能撞到 Meta 限制。
故障排查
Meta 仪表盘里设置校验失败("URL couldn't be validated")
几乎总是以下之一:
- 隧道 URL 错了或过期——cloudflared 快速隧道会轮换。拿新 URL,同时更新
.env和 Meta 仪表盘。 - Verify token 不匹配——
~/.hermes/.env中WHATSAPP_CLOUD_VERIFY_TOKEN必须与你敲进 Meta 仪表盘的完全一致。先跑上面的 curl 探针,确认网关本地的 verify 握手正常。 - 网关没在跑——检查
hermes gateway已起。 - App Secret 没设——没有它,Hermes 以 503 拒绝入站 POST。Meta 解读为"无法校验"。
graph error 100: Object with ID '...' does not exist
你把电话号码(10-11 位)粘进了 WHATSAPP_CLOUD_PHONE_NUMBER_ID,而不是 Phone Number ID(Meta 的 15-17 位内部 ID)。重看 API Setup 页——Phone Number ID 显示在 "From" 下拉下方。
向导现在用校验器抓住这个,但手动配置时值得知道。
graph error 190: Authentication Error
你的 access token 无效。子码:
subcode 463——token 过期。临时 token 24h。重新生成,或切到系统用户永久 token(见上)。subcode 467——token 被作废(吊销或改密码)。- 其他 190——生成 token 时没带所需权限。确认三个(
business_management、whatsapp_business_messaging、whatsapp_business_management)都选了。
graph error 131047: Re-engagement message
24 小时对话窗口过期了(见"已知限制")。要么:
- 让用户先私信机器人重开窗口。
- 等 Hermes 支持模板。
入站消息:media metadata fetch failed (status=401)
与出站(graph error 190)同一个 401 根因——access token 无效或过期。修 token。
机器人回复显示为原始 JSON / 工具调用泄漏
常见原因:为 whatsapp_cloud 配置的工具集缺了 agent 想调的工具。查 hermes tools list,确认平台用的是 hermes-whatsapp(默认 Cloud 适配器工具集,与 Baileys 相同)。
如果模型发出工具调用形态的文本而非结构化调用,通常意味着工具集实际上是空的。平台 → 默认工具集映射见 hermes_cli/platforms.py。
STT(语音转写)返回空 / "could not transcribe"
默认 stt.provider: local 需要 python -c "import pm; pm.sync_venv(['stt-whisper'], explicit=True)"。如果你是 Nous 订阅者,可以改走托管网关做 STT——在 hermes tools 里给语音转文本选 Nous Subscription,或直接设置:
hermes config set stt.provider nous
hermes gateway restart
这用你的 Nous Portal access token,无需单独 OpenAI key。(旧文档建议 stt.use_gateway true——那个标志是遗留;现在只由 provider 选择控制路由。)
安全说明
- 把 App Secret 当密码对待——任何人拿到它都能伪造 Hermes 会当作真实的 webhook 载荷。
- verify token 是共享密钥——泄露风险较低(最坏情况有人把 Meta webhook 重新订阅到他自己的另一个 URL),但仍避免提交它。
- access token 是你机器人的身份——系统用户 token 等价于长期 API key。部署被攻陷时立即轮换。
- 设了
WHATSAPP_CLOUD_APP_SECRET时 webhook 端点只接受签名请求——即使开发环境也保持设置。没有它,网关以 HTTP 503 拒绝入站投递。 /health端点无认证——暴露它是安全的,因为它只报告配置是否存在的布尔值,不报告值本身。但如果你不想暴露它,在反向代理/隧道层限制访问。
与 Baileys 桥接对比
Baileys(hermes whatsapp) | Cloud API(hermes whatsapp-cloud) | |
|---|---|---|
| 账号类型 | 个人 | 业务 |
| 设置 | 扫二维码 | Meta 应用 + WABA + token |
| 依赖 | Node.js + npm | 纯 Python(httpx + aiohttp) |
| 进程 | 托管 Node 子进程 | aiohttp webhook 服务器 |
| 需要公网 URL? | 否 | 是 |
| 封号风险 | 有(非官方 API) | 无(官方支持) |
| 入站 | 轮询 Node 桥接 | Meta webhook POST |
| 出站 | 本地桥接 → Baileys | HTTPS 到 graph.facebook.com |
| 群组 | 完整支持 | 仅私信(v1) |
| 24h 窗口 | 无限制 | 硬规则——之后需模板 |
| 语音消息(出) | 原生 | 有 ffmpeg 原生,否则 MP3 |
| 已读回执 | 无 | 有(蓝色双勾) |
| 正在输入指示 | 无 | 有(回复时自动消失) |
| 交互按钮 | 仅文本回退 | 原生(澄清、审批、斜杠确认) |
| 生产使用 | 有风险(Meta 可封) | 为此设计 |
跑个人项目的用户大多偏好 Baileys。跑面向客户机器人的用户大多偏好 Cloud API。
另见
- Meta 官方 WhatsApp Business Cloud API 文档——底层平台、定价、App Review 和 Meta 侧限流的权威参考。
- WhatsApp(Baileys 桥接)设置——个人项目的替代集成。
- 消息平台概览——所有消息集成一览。