跳到主要内容

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 仪表板)。

本页的其余部分是手动参考文档。


前提条件

  1. 一个 Meta Business 账户。在 business.facebook.com 创建一个。
  2. 一个启用了 WhatsApp 的 Meta 应用。参见下方的“创建 Meta 应用”。
  3. 一种通过 HTTPS 将本地端口暴露给公共互联网的方法。推荐使用 Cloudflare Tunnel (cloudflared)——免费,无需端口转发,无需域名。ngrok、带有反向代理 + TLS 的自有域名,或直接绑定到公网 IP 的 VPS 也都可以工作。
  4. 可选但推荐:在 PATH 中安装 ffmpeg,以便出站语音消息呈现为原生的 WhatsApp 语音笔记气泡(绿色波形),而不是 MP3 音频附件。如果缺失,Hermes 会优雅降级。

创建 Meta 应用

  1. 前往 developers.facebook.com/appsCreate App(创建应用)。
  2. 选择用例:"Connect with customers through WhatsApp"(通过 WhatsApp 与客户联系)→ Next(下一步)。
  3. 选择或创建一个业务组合。查看发布要求。确认 → Create app(创建应用)。
  4. 创建后,你将进入 Customize use case → Connect on WhatsApp → Quickstart(自定义用例 → 连接 WhatsApp → 快速入门)。点击 Start using the API(开始使用 API)→ 你现在位于 API Setup(API 设置)页面。
  5. 确保已链接 WhatsApp Business Account (WABA)。如果你在步骤 3 中创建了新的业务组合,系统会自动创建一个。请在 API 设置页面中验证。

你需要从仪表板中获取以下值——向导会按此顺序提示你输入:

仪表板中的位置字段格式备注
Phone Number IDApp Dashboard → WhatsApp → API Setup → "From" 下拉菜单下方数字,15-17 位不是电话号码本身。最常见的设置错误是将实际电话号码粘贴到这里。
Access TokenApp Dashboard → WhatsApp → API Setup → "Generate access token"EAA 开头,100+ 字符临时令牌有效期为 24 小时——参见下方的“永久令牌”以用于生产环境。
App SecretApp 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 永久令牌

  1. 前往 business.facebook.com/latest/settingsSystem users(系统用户,左侧边栏)。
  2. Add(添加)→ 名称(例如 hermes-bot)→ 角色:Admin(管理员)。
  3. 选择新用户 → Assign Assets(分配资产):
    • 选择你的应用 → 在 Full control(完全控制)下切换 Manage app(管理应用)。
    • 选择你的 WhatsApp 账户 → 在 Full control(完全控制)下切换 Manage WhatsApp Business Accounts(管理 WhatsApp Business 账户)。
    • 点击 Assign assets(分配资产)。
  4. 使用以下权限 Generate token(生成令牌):
    • business_management
    • whatsapp_business_messaging
    • whatsapp_business_management
  5. 设置 token expiration: Never(令牌过期:永不)。
  6. 复制令牌 → 更新 ~/.hermes/.env 中的 WHATSAPP_CLOUD_ACCESS_TOKEN → 重启网关。

除非你明确撤销,否则 System User 令牌不会过期。


将 Hermes 暴露给互联网

Cloud API 通过 HTTPS POST 将入站消息传递到你的 webhook URL——这意味着 Hermes 网关必须可以从 Meta 的服务器访问。三种常见方式:

免费,无需端口转发,适用于 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 会轮换

免费的快速隧道 URL 每次重启 cloudflared 时都会变化。若要获得稳定的 URL,请使用 cloudflared tunnel login 登录并创建命名隧道。免费的 Cloudflare 账户可获得无限数量的命名隧道 — 请参阅 Cloudflare 文档 了解命名隧道的工作流程。

ngrok

ngrok http 8090

免费层级在每次重启时会显示不同的 URL。付费层级提供稳定的子域名。

自有域名 + 反向代理

如果你已经拥有一台带有 TLS 证书(Caddy、nginx 等)的服务器,请将路由指向 localhost:8090。这是生产环境中最稳定的选项,但需要现有的基础设施。


在 Meta 侧配置 Webhook

隧道运行后:

  1. 记下隧道打印出的公共 URL — 例如 https://abc123.trycloudflare.com
  2. 生成一个 验证令牌(Verify Token) — 向导会使用 secrets.token_urlsafe(32) 为你生成;如果是手动配置,请运行:
    python -c "import secrets; print(secrets.token_urlsafe(32))"
    将其保存为 ~/.hermes/.env 中的 WHATSAPP_CLOUD_VERIFY_TOKEN
  3. 启动 Hermes 网关:hermes gateway
  4. 在 Meta App Dashboard → WhatsApp → Configuration(或根据 UI 版本不同,选择 Use cases → Customize → Configuration)→ 点击 Webhook 部分的 Edit
  5. 填写:
    • Callback URLhttps://abc123.trycloudflare.com/whatsapp/webhook
    • Verify Token:步骤 2 中的字符串(必须完全匹配)
  6. 点击 Verify and save。Meta 会通过 GET 请求访问你的 URL,网关回显挑战值,Meta 随后将 webhook 标记为已验证。
  7. 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 限制了你的机器人可以发送消息的号码:

  1. App Dashboard → WhatsApp → API Setup → To 下拉菜单。
  2. 点击 Manage phone number list
  3. 添加你想要发送消息的电话号码(你自己的、团队的、友好的测试者)。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_TOKENMeta 访问令牌(以 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_USERSfalse设置为 true 以绕过允许列表。
WHATSAPP_CLOUD_APP_ID可选,用于未来的分析集成。
WHATSAPP_CLOUD_WABA_ID可选,用于未来的分析集成。
WHATSAPP_CLOUD_WEBHOOK_HOST0.0.0.0Webhook 服务器绑定的接口。
WHATSAPP_CLOUD_WEBHOOK_PORT8090Webhook 服务器绑定的端口。必须与隧道转发的端口匹配。
WHATSAPP_CLOUD_WEBHOOK_PATH/whatsapp/webhookMeta POST 请求的 URL 路径。
WHATSAPP_CLOUD_API_VERSIONv20.0Meta 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:使用包管理器
  • 不包含 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_managementwhatsapp_business_messagingwhatsapp_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
出站本地桥接 → BaileysHTTPS 到 graph.facebook.com
群组完全支持仅限私聊(v1)
24小时窗口无限制硬性规定 — 之后需要模板消息
语音笔记(出站)原生支持原生支持需 ffmpeg,否则回退到 MP3
已读回执是(蓝色双勾)
输入指示器是(响应时自动消失)
交互式按钮仅文本回退原生支持(澄清、批准、斜杠确认)
生产环境使用有风险(Meta 可能封禁)为此设计

大多数为个人项目运行 Hermes 的用户更喜欢 Baileys。大多数运行面向客户的机器人的用户更喜欢 Cloud API。


另请参阅