WhatsApp Business Cloud API 设置
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"(通过 WhatsApp 与客户联系)→ Next(下一步)。
- 选择或创建一个业务组合。查看发布要求。确认 → Create app(创建应用)。
- 创建后,你将进入 Customize use case → Connect on WhatsApp → Quickstart(自定义用例 → 连接 WhatsApp → 快速入门)。点击 Start using the API(开始使用 API)→ 你现在位于 API Setup(API 设置)页面。
- 确保已链接 WhatsApp Business Account (WABA)。如果你在步骤 3 中创建了新的业务组合,系统会自动创建一个。请在 API 设置页面中验证。
你需要从仪表板中获取以下值——向导会按此顺序提示你输入:
| 值 | 仪表板中的位置 | 字段格式 | 备注 |
|---|---|---|---|
| Phone Number ID | App Dashboard → WhatsApp → API Setup → "From" 下拉菜单下方 | 数字,15-17 位 | 不是电话号码本身。最常见的设置错误是将实际电话号码粘贴到这里。 |
| Access Token | App Dashboard → WhatsApp → API Setup → "Generate access token" | 以 EAA 开头,100+ 字符 | 临时令牌有效期为 24 小时——参见下方的“永久令牌”以用于生产环境。 |
| App Secret | App Dashboard → Settings → Basic → 点击 App secret 旁边的 "Show" | 32 位小写十六进制 | 用于验证传入的 webhook 签名。如果没有它,入站交付将被拒绝并返回 503。 |
| App ID(可选) | App Dashboard → Settings → Basic | 数字,15-16 位 | 消息传递不需要,对分析有用。 |
| WABA ID(可选) | App Dashboard → WhatsApp → API Setup → 靠近顶部 | 数字,15+ 位 | 消息传递不需要,对分析有用。 |
永久令牌(生产环境)
临时访问令牌在 24 小时后过期,这意味着今天生成的令牌明天将停止工作。对于生产部署,请使用 System User 永久令牌:
- 前往 business.facebook.com/latest/settings → System users(系统用户,左侧边栏)。
- Add(添加)→ 名称(例如
hermes-bot)→ 角色:Admin(管理员)。 - 选择新用户 → Assign Assets(分配资产):
- 选择你的应用 → 在 Full control(完全控制)下切换 Manage app(管理应用)。
- 选择你的 WhatsApp 账户 → 在 Full control(完全控制)下切换 Manage WhatsApp Business Accounts(管理 WhatsApp Business 账户)。
- 点击 Assign assets(分配资产)。
- 使用以下权限 Generate token(生成令牌):
business_managementwhatsapp_business_messagingwhatsapp_business_management
- 设置 token expiration: Never(令牌过期:永不)。
- 复制令牌 → 更新
~/.hermes/.env中的WHATSAPP_CLOUD_ACCESS_TOKEN→ 重启网关。
除非你明确撤销,否则 System User 令牌不会过期。
将 Hermes 暴露给互联网
Cloud API 通过 HTTPS POST 将入站消息传递到你的 webhook URL——这意味着 Hermes 网关必须可以从 Meta 的服务器访问。三种常见方式:
Cloudflare Tunnel(推荐)
免费,无需端口转发,适用于 Windows / macOS / Linux。作为独立进程与网关并行运行。
安装:
# Windows
winget install Cloudflare.cloudflared
# macOS
brew install cloudflared
# Linux
# Download the binary from 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,网关回显挑战值,Meta 随后将 webhook 标记为已验证。
- 在 Webhook fields 下,点击 Manage → 订阅 messages 字段。这告诉 Meta 实际将入站消息传递到你的 webhook。
手动验证循环(从第三个终端执行):
TUNNEL="https://abc123.trycloudflare.com"
VERIFY="<your verify token>"
# Should print HTTP 200 with body "hello"
curl -i "$TUNNEL/whatsapp/webhook?hub.mode=subscribe&hub.verify_token=$VERIFY&hub.challenge=hello"
# Health endpoint — should show verify_token_configured: true and app_secret_configured: true
curl "$TUNNEL/health"
收件人白名单(Meta 侧)
在开发模式下(在你的应用通过应用审核之前),Meta 限制了你的机器人可以发送消息的号码:
- App Dashboard → WhatsApp → API Setup → To 下拉菜单。
- 点击 Manage phone number list。
- 添加你想要发送消息的电话号码(你自己的、团队的、友好的测试者)。Meta 会通过短信或 WhatsApp 向每个号码发送一个 6 位数的验证码。
开发模式下最多支持 5 个号码。通过应用审核后,此限制将被移除。
允许列表(Hermes 侧)
除了 Meta 的收件人白名单外,Hermes 还有自己的每平台允许列表,用于控制 代理处理哪些入站消息。添加到 ~/.hermes/.env:
# Comma-separated phone numbers, country code, no '+' / spaces / dashes
WHATSAPP_CLOUD_ALLOWED_USERS=15551234567,15557654321
# Or allow everyone (only safe in combination with Meta's recipient whitelist)
# WHATSAPP_CLOUD_ALLOW_ALL_USERS=true
向导会在第 6 步设置此项。如果没有允许列表,所有入站消息都会被拒绝 — 这是有意为之,以防止在收件人白名单放宽时,机器人被随机号码调用。
完善机器人的 WhatsApp 个人资料
WhatsApp 会在聊天标题和联系人列表中显示机器人的 名称和个人资料图片。这些无法通过 Cloud API 设置 — 它们位于 Meta 的 Business Manager 中。
一旦你的机器人正常工作,请访问 business.facebook.com/wa/manage/phone-numbers,点击你的电话号码,你将找到:
| 内容 | 位置 | 备注 |
|---|---|---|
| 显示名称 | 电话号码页面顶部 | 更改需经过 Meta 的名称审核流程(约 24–48 小时)。 |
| 个人资料图片 | 电话号码页面顶部 | 正方形图片,建议 ≥640×640px。立即更新。 |
| 关于 / 描述 / 网站 / 电子邮件 / 营业时间 / 类别 | “Edit profile”按钮 | 当用户点击机器人名称时,这些信息会出现在信息面板中。仅用于展示。 |
| 验证徽章(绿色对勾) | Business Manager → Security Center → Start Verification | 需要 Meta 单独的商家验证流程。 |
hermes whatsapp-cloud 向导会在设置结束时打印这些链接。这些都不是机器人工作所必需的 — 它们纯粹是为了优化机器人在用户眼中的外观。
配置参考
所有设置都位于 ~/.hermes/.env 中。必需的值以 粗体 显示。
| 变量 | 默认值 | 描述 |
|---|---|---|
WHATSAPP_CLOUD_PHONE_NUMBER_ID | — | 来自 API 设置的 15-17 位 ID。不是电话号码。 |
WHATSAPP_CLOUD_ACCESS_TOKEN | — | Meta 访问令牌(以 EAA 开头)。临时令牌有效期为 24 小时,或使用系统用户永久令牌。 |
WHATSAPP_CLOUD_APP_SECRET | — | 来自 Settings → Basic 的 32 字符十六进制字符串。如果没有它,入站请求将被拒绝并返回 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 | 0.0.0.0 | 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 | — | 用作机器人主通道(用于 cron 任务等)的 wa_id。 |
你可以同时启用 Baileys (whatsapp) 和 Cloud (whatsapp_cloud) 适配器,并针对不同的电话号码。
功能
入站 (Inbound)
- 文本消息 — 直接传递给代理。
- 图片 — 自动下载并附加到代理的输入中。具有原生视觉能力的模型(Claude、GPT-4o、Gemini 等)直接读取图片;非视觉模型接收自动生成的文本描述。
- 语音笔记 — 自动下载为
.ogg格式,通过你配置的 STT 提供商(本地 faster-whisper、OpenAI/Nous、Groq 等)进行转录,然后作为文本交给代理。 - 文档 — 自动下载。小型可读文本文件(
.txt、.md、.json、.py、.csv等),最大 100KB,会被内联到代理的输入中,以便其无需调用工具即可读取。较大的文件会在本地缓存,供代理的其他工具访问。 - 按钮点击 — 当用户点击机器人之前发送的按钮(澄清选择、命令批准、斜杠命令确认)时,点击事件会直接路由到正确的处理程序。过期的点击会回退为被视为常规文本输入。
- 回复上下文 — 当用户回复之前的机器人消息时,代理会将原始消息视为上下文。
出站 (Outbound)
- 文本 — Markdown 会自动转换为 WhatsApp 风格的语法(
**bold**→*bold*,~~strike~~→~strike~,标题 → 粗体,[link](url)→link (url))。长消息会以每块 4096 个字符进行分割。 - 图片 — 支持代理生成的图片和本地图片文件,作为原生照片附件发送。
- 语音消息 — 文本转语音 (TTS) 输出通过 ffmpeg 转换为原生的 WhatsApp 语音笔记气泡(绿色波形)。如果未安装 ffmpeg,则回退为 MP3 音频附件。请参阅下方的“语音消息”。
- 视频 / 文档 — 均受支持,作为原生附件发送。
交互式用户体验 (Interactive UX)
当代理调用以下任何流程时,Hermes 会使用 WhatsApp 的原生交互式消息——使用点击即答按钮,而不是“回复数字”提示:
clarify工具 — 多项选择题呈现为快速回复按钮(1–3 个选项)或点击打开的列表面板(4 个及以上选项)。选择“✏️ Other”允许用户输入自由形式的答案,代理将其接收为最终结果。- 危险命令批准 — 当代理的终端/代码执行遇到受限命令时,用户会看到
✅ Approve/❌ Deny按钮,而无需输入/approve或/deny。 - 斜杠命令确认 — 特权命令如
/reload-mcp会显示✅ Approve Once/🔒 Always/❌ Cancel按钮。
如果按钮无法渲染(例如在旧版 WhatsApp 客户端上),所有交互式提示都会优雅地降级为纯文本。
已读回执和输入指示器
Hermes 会立即确认入站消息:
- 一旦网关收到消息,你的消息就会显示蓝色双勾。
- 当代理准备回复时,你在 WhatsApp 聊天中的机器人名称会显示**“typing…”**(正在输入…)。
- 当机器人的第一条响应消息到达时,输入指示器会自动消失。
这使得用户可以清楚地知道机器人是已经看到了你的消息,还是仍在处理响应。
语音消息
WhatsApp 区分“语音笔记”(绿色波形气泡)和通用音频文件附件。区别仅在于编解码器:语音笔记需要是使用 opus 编码的 audio/ogg 格式。
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
# look for "ffmpeg_present": true
已知限制
24 小时对话窗口
Meta 仅允许在用户最后一条 inbound 消息后的 24 小时窗口内发送自由格式消息。超出该窗口后,Meta 的 API 仅接受预批准的消息模板。
实际影响:
- 响应式聊天(用户私信 → 机器人在 24 小时内回复 → 用户回复 → ...)可以永久持续。这涵盖了 >95% 的常规机器人使用场景。
- 间隔超过 24 小时后向 WhatsApp 发送消息的 Cron 任务将失败,并返回 Graph 错误代码
131047(“Re-engagement message”)。 - 耗时超过 24 小时的长期运行的
delegate_task异步结果也会以相同方式失败。 - 将外部事件路由到 WhatsApp 的 Webhook 订阅者在用户最近未私信机器人时会失败。
Hermes 会在其系统提示中告知代理此窗口限制,因此模型在安排延迟消息时知道要提及这一点。
消息模板支持(用于窗口外发送的变通方案)尚未在 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 仪表板。 - 验证令牌不匹配 —
~/.hermes/.env中的WHATSAPP_CLOUD_VERIFY_TOKEN必须与你在 Meta 仪表板中输入的内容完全一致。先运行上述 curl 探测命令,确认网关的验证握手在本地正常工作。 - 网关未运行 — 检查
hermes gateway是否正在运行。 - 未设置 App Secret — 如果没有设置,Hermes 会以 503 拒绝 inbound POST 请求。Meta 将其解释为“无法验证”。
graph error 100: Object with ID '...' does not exist
你将电话号码(10-11 位数字)粘贴到了 WHATSAPP_CLOUD_PHONE_NUMBER_ID 中,而不是电话号码 ID(Meta 的 15-17 位内部 ID)。重新检查 API 设置页面 — 电话号码 ID 显示在“From”下拉菜单下方。
向导现在会通过验证器捕获此错误,但如果你手动配置,了解这一点很有帮助。
graph error 190: Authentication Error
你的访问令牌无效。子代码:
subcode 463— 令牌已过期。临时令牌有效期为 24 小时。重新生成,或切换到系统用户永久令牌(见上文)。subcode 467— 令牌已失效(被撤销或密码已更改)。- 其他 190 — 生成令牌时未包含所需的权限。确保选中了所有三个权限(
business_management、whatsapp_business_messaging、whatsapp_business_management)。
graph error 131047: Re-engagement message
24 小时对话窗口已过期(参见“已知限制”)。你可以:
- 要求用户先私信机器人以重新打开窗口。
- 等待 Hermes 实现模板支持。
Inbound message: media metadata fetch failed (status=401)
与外发相同的 401 根本原因(graph error 190)— 访问令牌无效或已过期。修复令牌即可。
机器人回复显示为原始 JSON / 工具调用泄露
常见原因:为 whatsapp_cloud 配置的工具集缺少代理想要调用的工具。检查 hermes tools list 并验证平台是否正在使用 hermes-whatsapp(默认的 Cloud 适配器工具集,与 Baileys 相同)。
如果模型发出类似工具调用的文本而不是结构化调用,通常意味着工具集实际上为空。请参阅 hermes_cli/platforms.py 了解平台到默认工具集的映射。
STT(语音消息转录)返回空值 / “could not transcribe”
默认的 stt.provider: local 需要 pip install faster-whisper。如果你是 Nous 订阅用户,可以通过 Meta 的托管音频网关路由 STT:
hermes config set stt.provider openai
hermes config set stt.use_gateway true
hermes gateway restart
这使用你的 Nous Portal 访问令牌,而无需单独的 OpenAI 密钥。
安全说明
- 将 App Secret 视为密码 — 任何拥有它的人都可以伪造 Hermes 会接受为真实的有效 webhook 负载。
- verify token 是一个共享密钥 — 泄露的风险较低(最坏的情况是有人可以将 Meta 的 webhook 重新订阅到他们的其他 URL),但仍应避免将其提交到代码库中。
- access token 是你的机器人身份 — System User 令牌等同于长期有效的 API 密钥。如果部署遭到入侵,请立即轮换。
- 当设置
WHATSAPP_CLOUD_APP_SECRET时,webhook 端点仅接受签名请求 — 即使在开发环境中也要保持设置。如果没有它,网关将拒绝入站交付并返回 HTTP 503。 /health端点未经身份验证 — 暴露它是安全的,因为它只报告配置存在的布尔值,而不报告值本身。但如果你不想暴露它,可以在反向代理/隧道层限制访问。
与 Baileys 桥接的比较
Baileys (hermes whatsapp) | Cloud API (hermes whatsapp-cloud) | |
|---|---|---|
| 账户类型 | 个人 | 商业 |
| 设置 | 扫描二维码 | Meta 应用 + WABA + 令牌 |
| 依赖项 | Node.js + npm | 纯 Python (httpx + aiohttp) |
| 进程 | 托管的 Node 子进程 | aiohttp webhook 服务器 |
| 需要公共 URL? | 否 | 是 |
| 账户封禁风险 | 是(非官方 API) | 否(官方支持) |
| 入站 | 轮询 Node 桥接 | 来自 Meta 的 Webhook POST |
| 出站 | 本地桥接 → Baileys | HTTPS 到 graph.facebook.com |
| 群组 | 完全支持 | 仅限私聊(v1) |
| 24小时窗口 | 无限制 | 硬性规定 — 之后需要模板消息 |
| 语音笔记(出站) | 原生支持 | 原生支持需 ffmpeg,否则回退到 MP3 |
| 已读回执 | 否 | 是(蓝色双勾) |
| 输入指示器 | 否 | 是(响应时自动消失) |
| 交互式按钮 | 仅文本回退 | 原生支持(澄清、批准、斜杠确认) |
| 生产环境使用 | 有风险(Meta 可能封禁) | 为此设计 |
大多数为个人项目运行 Hermes 的用户更喜欢 Baileys。大多数运行面向客户的机器人的用户更喜欢 Cloud API。
另请参阅
- Meta 官方 WhatsApp Business Cloud API 文档 — 底层平台、定价、应用审核和 Meta 侧速率限制的权威参考。
- WhatsApp (Baileys 桥接) 设置 — 个人项目的替代集成方案。
- 消息平台概览 — 所有消息集成一览。