给 Hermes 装上 Webhook:让业务系统主动把通知推到 Telegram/Discord/飞书


很多业务系统的第一个「自动化通知」需求都长得差不多:当订单状态变化、支付成功、监控告警或 CI 跑完时,往群里发一条消息。最省事的方案通常是写一段脚本轮询数据库或 API,然后把消息丢到 Telegram、Discord、Slack 或飞书。

轮询能跑,但长期看有几个硬伤:

  • 延迟和 API 配额:每分钟扫一次数据库,大多数请求都是空跑;想降到秒级,API 压力和成本又翻倍。
  • 耦合:业务系统里要硬编码消息格式和聊天平台 token,哪天换平台或换群,就得改业务代码。
  • 扩展性:同样的告警,有时候要发给 Telegram,有时候要发给飞书,有时候要让 AI 先总结一遍再发。轮询脚本会越写越复杂。

Hermes Agent 自带一个 webhook 平台,本质上是一个 HTTP 事件接收器:业务系统有事件就 POST 过来,Hermes 验证签名、渲染模板,然后直接把消息推送到你配置好的聊天平台。如果事件只需要纯通知、不需要 AI 总结,可以开启 Direct Delivery 模式——不调用 LLM、不进 agent loop,亚秒内完成投递,且完全不消耗 token。

这篇文章是一份面向业务系统的 webhook 实战指南:从启用平台、配置路由,到三个真实场景的完整配置,再到安全加固和排错。

两种工作模式:Agent 处理 vs. Direct Delivery

Hermes webhook 支持两种投递路径,先搞清楚区别,后面选型不会错。

模式 是否调用 LLM 适用场景 延迟 成本
Agent 处理 需要 AI 理解、总结、决策后再回复或转发 秒级 消耗 token
Direct Delivery 纯通知:订单状态、支付成功、监控告警 亚秒级 零 LLM 成本

Direct Delivery 是这篇文章的重点。它的逻辑很简单:

  1. 业务系统按约定签名,POST 一个 JSON 到 https://your-server:8644/webhooks/<route-name>
  2. Hermes 验证 HMAC 签名,确认请求来自可信来源。
  3. 用模板把 JSON 字段渲染成一条可读消息。
  4. 直接投递到 Telegram、Discord、Slack、飞书等平台,返回 200 OK

整个流程没有 LLM 调用,所以速度、成本和稳定性都更接近一个传统的消息网关——但配置和扩展性又保留了 Hermes 的灵活。

第一步:启用 webhook 平台

有两种方式启用。简单场景用环境变量,复杂或长期部署用 config.yaml

环境变量方式(最快)

~/.hermes/.env 里加三行:

WEBHOOK_ENABLED=true
WEBHOOK_PORT=8644        # 默认值
WEBHOOK_SECRET=your-global-secret

然后重启 gateway:

hermes gateway restart

确认服务在跑:

curl http://localhost:8644/health

返回 {"status": "ok", "platform": "webhook"} 即成功。

配置方式(推荐生产用)

~/.hermes/config.yaml 里声明 platforms.webhook

platforms:
  webhook:
    enabled: true
    extra:
      port: 8644
      secret: "your-global-secret"

重启 gateway 后,Hermes 会监听 8644 端口。如果服务器在公网,需要确保防火墙放行该端口;如果在内网或没有公网 IP,可以用 Cloudflare Tunnel 或 ngrok 暴露出来。

第二步:配置一个业务通知路由

路由定义在 platforms.webhook.extra.routes 下。每个路由有名字、事件、secret、模板和投递目标。下面是一个电商订单通知的完整例子,把新订单推送到 Telegram。

platforms:
  webhook:
    enabled: true
    extra:
      port: 8644
      secret: "global-fallback-secret"
      routes:
        order-notify:
          events: ["order.created"]
          secret: "shopify-webhook-secret"
          prompt: |
            🛒 新订单 #{order.id}
            金额:{order.total_price} {order.currency}
            客户:{order.customer.email}
            商品:{order.line_items[0].title}
          deliver: "telegram"
          deliver_only: true
          deliver_extra:
            chat_id: "-1001234567890"

配置要点:

  • events 是可选的。如果业务系统通过 X-Webhook-Eventevent_type 字段发送事件类型,这里可以只接收 order.created
  • secret 用于 HMAC 签名验证。如果路由没写 secret,会回退到全局 secret
  • deliver_only: true 开启 Direct Delivery,不调用 LLM。
  • prompt 是模板,用 {order.total_price} 这种点号语法访问 JSON 字段。{__raw__} 可以 dump 整个 payload。
  • deliver_extra.chat_id 指定目标群。如果不写,Hermes 会投递到该平台的 home channel(前提是该平台已在 gateway 里启用并连接)。

第三步:业务系统发送事件

业务系统需要把事件 POST 到正确的 URL。以电商系统为例,URL 是:

POST https://your-server:8644/webhooks/order-notify

建议用 Generic V2 签名:把 timestamp.body 拼起来算 HMAC-SHA256,放在 X-Webhook-Signature-V2 头里,同时带上 X-Webhook-Timestamp 头。时间戳有效窗口是 ±300 秒,能防止重放攻击。

import hmac
import hashlib
import time
import json
import requests

secret = b"shopify-webhook-secret"
body = json.dumps({
    "event_type": "order.created",
    "order": {
        "id": 10086,
        "total_price": "199.00",
        "currency": "USD",
        "customer": {"email": "[email protected]"},
        "line_items": [{"title": "Hermes 贴纸包"}]
    }
}).encode()

timestamp = str(int(time.time()))
signature = hmac.new(secret, f"{timestamp}.{body.decode()}".encode(), hashlib.sha256).hexdigest()

requests.post(
    "https://your-server:8644/webhooks/order-notify",
    data=body,
    headers={
        "Content-Type": "application/json",
        "X-Webhook-Signature-V2": signature,
        "X-Webhook-Timestamp": timestamp,
    }
)

GitHub/GitLab 也支持,它们的签名机制会被自动识别。如果是自己写的业务系统,优先用 Generic V2。

第四步:测试路由

不用等业务系统上线,先在本地测试:

hermes webhook test order-notify \
  --payload '{"event_type":"order.created","order":{"id":10086,"total_price":"199.00","currency":"USD","customer":{"email":"[email protected]"},"line_items":[{"title":"Hermes 贴纸包"}]}}'

hermes webhook test 会模拟一次 POST,帮你验证模板渲染和投递是否正确。如果消息没收到,打开 gateway.log 或在前台运行 hermes gateway run,看是签名失败、事件被过滤,还是投递目标没连上。

场景一:电商订单通知 → Telegram

上面的 order-notify 已经是一个完整例子。关键细节:

  • Telegram 群聊 chat_id 通常是负数(-100 开头)。
  • 如果希望发到特定论坛话题,在 deliver_extra 里加 message_thread_id: "42"
  • 模板里 {order.line_items[0].title} 只取第一个商品;如果要列出全部,建议用 {__raw__} 让 AI 在 agent 模式下总结,或者业务系统预处理成简短字符串。

场景二:支付成功 → Discord

支付系统通常已经有 webhook。这里把 Stripe/支付宝/微信支付的成功事件转成 Discord 消息:

routes:
  payment-success:
    events: ["payment_intent.succeeded"]
    secret: "stripe-webhook-secret"
    prompt: |
      💰 收到付款 {amount} {currency}
      订单:{metadata.order_id}
      客户:{receipt_email}
    deliver: "discord"
    deliver_only: true
    deliver_extra:
      chat_id: "123456789012345678"

对于 Stripe,events 对应 X-Stripe-Event 头,但 Hermes 的 webhook 平台识别的是 X-GitHub-EventX-GitLab-Eventevent_type。如果 Stripe 没有 event_type,可以在业务系统侧包装一层,或者使用空 events 接收所有 POST,再用 filters 过滤。

routes:
  payment-success:
    secret: "stripe-webhook-secret"
    filters:
      - field: "type"
        equals: "payment_intent.succeeded"
    prompt: "..."
    deliver: "discord"
    deliver_only: true

场景三:监控告警 → 飞书

Grafana、Datadog 或自研监控系统的告警通常带严重级别。只把 critical 发到飞书:

routes:
  critical-alert:
    events: ["alert"]
    secret: "monitoring-webhook-secret"
    filters:
      - field: "severity"
        equals: "critical"
    prompt: |
      🚨 严重告警
      服务:{service}
      指标:{metric}
      当前值:{current_value}
      阈值:{threshold}
    deliver: "feishu"
    deliver_only: true
    deliver_extra:
      chat_id: "your_feishu_chat_id"

飞书平台需要在 gateway 里启用并配置好。如果只用 webhook 通知,不需要把飞书作为主聊天平台,只要正确配置 platforms.feishu 的 home channel 或在 deliver_extra 里指定 chat_id 即可。

更高级:用脚本过滤和转换 payload

如果业务系统的 JSON 很脏,或者只想通知特定条件,可以写一个脚本做预处理。脚本必须放在 ~/.hermes/scripts/ 下,相对路径解析到这个目录。

# ~/.hermes/scripts/alert-filter.py
import json
import sys

payload = json.load(sys.stdin)
if payload.get("severity") != "critical":
    print("[SILENT]")
    raise SystemExit(0)

payload["body"] = f"{payload['service']} critical: {payload['metric']}"
print(json.dumps(payload))

然后在路由里引用:

routes:
  critical-alert:
    events: ["alert"]
    secret: "monitoring-webhook-secret"
    script: "alert-filter.py"
    prompt: "{body}"
    deliver: "feishu"
    deliver_only: true

脚本输出 JSON 会替换 payload,文本输出会作为 script_output 注入,空输出或 [SILENT] 会让 Hermes 忽略这次 webhook。

安全:不要只依赖 HMAC

HMAC 验证只能证明发送方可信,不能证明 payload 里的内容可信。PR 标题、issue 内容、订单备注、告警消息都是第三方生成的文本,理论上可以被注入指令。因此:

  1. ** webhook 脚本和 agent 运行环境尽量隔离**。如果暴露在公网,用 Docker/SSH 终端后端,不要把 host 直接暴露给事件。
  2. Direct Delivery 模式天然更安全,因为不调用 LLM,不存在 prompt injection 触发 agent 动作的风险。
  3. 按需限制工具集。如果某条路由必须进入 agent 模式,可以在 skills 或工具集层面禁用 terminalfile 等危险工具。
  4. 审批保持开启。如果 webhook 触发的 agent 需要执行命令,保持审批流程,防止被注入的指令自动执行。
  5. 模板尽量窄。不要滥用 {__raw__},只把必要的字段写进 prompt

另外,每条路由默认有 30 次/分钟 的速率限制、1 MB 的 body 大小限制,以及 1 小时 的 idempotency 缓存。这些默认值对大多数业务通知够用,必要时可在 extra 里调整:

extra:
  rate_limit: 60
  max_body_bytes: 2097152

排错清单

症状 可能原因 排查方法
业务系统 POST 失败 端口/防火墙未开放 curl http://your-server:8644/health
返回 401 签名错误 检查 secret 和 HMAC 算法;看 gateway 日志
返回 200 但消息没收到 事件类型不匹配 检查 events 列表和 event_type 字段
重复消息 上游重试 + idempotency 没命中 确保上游带 X-Request-IDX-GitHub-Delivery
模板变量没展开 字段名写错 hermes webhook test 反复调试

动态订阅 vs. 配置文件

除了 config.yaml 里的静态路由,还可以用 CLI 动态创建订阅:

hermes webhook subscribe order-notify \
  --events "order.created" \
  --prompt "新订单 #{order.id},金额 {order.total_price} {order.currency}" \
  --deliver telegram \
  --deliver-chat-id "-1001234567890" \
  --deliver-only \
  --description "电商订单通知"

动态订阅存在 ~/.hermes/webhook_subscriptions.json,gateway 会热加载,不需要重启。静态路由同名时优先于动态订阅。具体子命令参考我们站上的 hermes webhook 命令文档

总结

Hermes 的 webhook 平台不是一个聊天机器人,而是一个事件驱动的消息路由器。对业务系统来说,它最大的价值是:

  • 把「轮询」变成「推送」,省 API 配额、降延迟。
  • 把「业务系统里写消息格式」变成「Hermes 里统一配置模板」,换平台不改业务代码。
  • Direct Delivery 模式让纯通知场景零 LLM 成本、亚秒投递。

如果你已经用 Hermes 处理日常任务,那给它加一个 webhook 入口几乎是免费的——同一个 gateway 已经在跑,只是多监听一个端口。业务系统从此可以主动说话,而你只需要决定它说什么、说到哪。

还没安装 Hermes?从安装指南开始。想了解 webhook 的完整命令和参数,参考hermes webhook 命令文档。对事件驱动的自动化感兴趣,也可以看看我们的 cron 脚本任务指南yolo 模式解析