Microsoft Graph Webhook 监听器

msgraph_webhook 网关平台是一个入站事件监听器。它是 Hermes 接收来自 Microsoft Graph 变更通知的方式——“Teams 会议已结束”、“此聊天中有新消息”、“此日历事件已更新”。与 teams 平台(用户输入消息的聊天机器人)不同——这个是 M365 告诉 Hermes 发生了某事,而不是一个人。

目前主要的消费者是 Teams 会议摘要流水线:当会议产生转录时 Graph 通知,流水线获取该转录,Hermes 将摘要发布回 Teams。其他 Graph 资源(/chats/.../messages/users/.../events)使用相同的监听器——流水线消费者会通过各自的 PR 接入。

前提条件

  • Microsoft Graph 应用程序凭证——注册 Microsoft Graph 应用程序
  • Microsoft Graph 可以访问的公共 HTTPS URL(Graph 不会调用私有端点)。开发隧道可用于测试;生产环境需要具有有效证书的真实域名。
  • 用作 clientState 值的强共享密钥。使用 openssl rand -hex 32 生成,并将其放入 ~/.hermes/.env 作为 MSGRAPH_WEBHOOK_CLIENT_STATE

快速开始

最小 ~/.hermes/config.yaml

platforms:
  msgraph_webhook:
    enabled: true
    extra:
      port: 8646
      client_state: "replace-with-a-strong-secret"
      accepted_resources:
        - "communications/onlineMeetings"

或通过 ~/.hermes/.env 中的环境变量(启动时自动合并):

MSGRAPH_WEBHOOK_ENABLED=true
MSGRAPH_WEBHOOK_PORT=8646
MSGRAPH_WEBHOOK_CLIENT_STATE=<generate-with-openssl-rand-hex-32>
MSGRAPH_WEBHOOK_ACCEPTED_RESOURCES=communications/onlineMeetings

启动网关:hermes gateway run。监听器暴露:

  • POST /msgraph/webhook — 来自 Graph 的变更通知
  • GET /msgraph/webhook?validationToken=... — Graph 订阅验证握手
  • GET /health — 就绪探测,带有接受/重复计数器

将监听器公开暴露(反向代理、开发隧道、入口)。Graph 订阅的通知 URL 是您的公共 HTTPS 源后跟 /msgraph/webhook

https://ops.example.com/msgraph/webhook

配置

所有设置位于 platforms.msgraph_webhook.extra 下:

设置默认值描述
host0.0.0.0HTTP 监听器的绑定地址。
port8646绑定端口。
webhook_path/msgraph/webhookGraph POST 的 URL 路径。
health_path/health就绪端点。
client_stateGraph 在每条通知中回显的共享密钥。使用 hmac.compare_digest 比较——用 openssl rand -hex 32 生成。
accepted_resources[](接受所有)Graph 资源路径/模式的白名单。尾部 * 表示前缀匹配。前导 / 可容忍。示例:["communications/onlineMeetings", "chats/*/messages"]
max_seen_receipts5000通知 ID 的去重缓存大小。达到上限时驱逐最旧的条目。
allowed_source_cidrs[](允许所有)可选的源 IP 白名单。请参见下文。

每个设置也有等效的环境变量(MSGRAPH_WEBHOOK_*),在网关启动时合并到配置中——请参阅环境变量参考

安全加固

clientState 是主要认证检查

每条 Graph 通知都包含您的订阅注册时使用的 clientState 字符串。监听器使用时序安全比较拒绝任何 clientState 不匹配的通知。这是 Microsoft 记录在案的机制——将此值视为强共享密钥。

如果 client_state 未设置,监听器会接受每个格式正确的 POST。生产环境中不要在没有它的情况下运行。

源 IP 白名单(生产部署)

对于生产环境,将监听器限制为 Microsoft 发布的 Graph webhook 源 IP 范围。Microsoft 在 Office 365 IP 地址和 URL Web 服务 下记录了出口范围。配置如下:

platforms:
  msgraph_webhook:
    enabled: true
    extra:
      client_state: "..."
      allowed_source_cidrs:
        - "52.96.0.0/14"
        - "52.104.0.0/14"
        # ...添加当前的 Microsoft 365 "Common" + "Teams" 类别出口范围

或作为环境变量:

MSGRAPH_WEBHOOK_ALLOWED_SOURCE_CIDRS="52.96.0.0/14,52.104.0.0/14"

空白名单 = 接受来自任何地方的请求(默认;保留开发隧道工作流)。无效的 CIDR 字符串会记录警告并被忽略。每季度检查 Microsoft IP 列表——它会变化。

HTTPS 终止

监听器使用纯 HTTP。在您的反向代理(Caddy、Nginx、Cloudflare Tunnel、AWS ALB)处终止 TLS,并通过本地网络代理到监听器。Graph 拒绝将消息投递到非 HTTPS 端点,因此没有路径可以让未加密的流量从 Graph 到达您。

响应卫生

成功时监听器返回 202 Accepted,响应体为空——内部计数器不包含在线路响应中。操作员可以通过 /health 观察计数。

状态码表:

结果状态
通知已接受或去重202
验证握手(带 validationToken 的 GET)200(回显令牌)
批次中每个项目都失败 clientState403
JSON 格式错误 / 缺少 value 数组 / 未知资源400
源 IP 不在白名单中403
不带 validationToken 的裸 GET400

故障排查

问题检查什么
Graph 订阅验证失败公共 URL 可达,/msgraph/webhook 路径匹配,带 validationToken 的 GET 在 10 秒内以 text/plain 原样回显令牌。
通知 POST 了但未被摄取client_state 与注册订阅时使用的匹配。如果值偏移,重新运行 openssl rand -hex 32 并创建新订阅。检查 accepted_resources 是否包含 Graph 发送的资源路径。
每条通知都返回 403clientState 不匹配(伪造的,或订阅注册时使用了不同的值)。使用 hermes teams-pipeline subscribe --client-state "$MSGRAPH_WEBHOOK_CLIENT_STATE" ... 重新创建订阅(随流水线运行时 PR 提供)。
监听器启动但 curl http://localhost:8646/health 挂起端口绑定冲突。检查 `ss -tlnp
来自 Microsoft 的真实 Graph 请求返回 403源 IP 白名单太窄。临时移除 allowed_source_cidrs,确认流量正常,然后扩大列表以包含当前的 Microsoft 出口范围。

相关文档