Webhook / Update 协议
统一说明 Update 事件结构、Webhook 验签和幂等处理。
Update 顶层结构
{
"update_id": 10001,
"type": "message",
"message": {
"message_id": "msg_xxx",
"chat": {
"type": "private",
"id": "au_xxx"
},
"from": {
"type": "user",
"openid": "au_xxx"
},
"msgType": "text",
"data": {
"content": "hello"
}
}
}
关键字段
| 字段 | 说明 |
update_id | 事件递增 ID,用于去重和 offset 推进。 |
type | 事件类型,如 message、callback_query。 |
chat.type | private 或 group。 |
from.type | 来源对象类型,常见为 user 或 bot。 |
from.openid | Bot 作用域内的用户标识。 |
msgType | 消息类型,第一版主路径以 text 为主。 |
Webhook 验签
第一版签名规则采用 signed_update_v1 包装。目标是让第三方开发者不需要依赖 Fly 服务端源码,也能独立完成验签。
- Webhook 必须验证平台签名,不能只按来源 IP 信任请求。
- 签名失败、时间戳过期、重复请求都必须拒绝。
- 如果需要重放验证,请保留原始请求体和关键 header。
- Phase 0 的密钥口径是:
webhook_secret = sha256(bot_token)。
字段说明
| 字段 | 说明 |
update_id | 全局递增 Update ID;Webhook 重试时保持不变。 |
type | Update 类型,如 message 或 callback_query。 |
bot.id | Bot public_id。 |
timestamp | 签名时间戳,秒级 int,用于防重放窗口。 |
nonce | 随机串,配合 timestamp 降低重放风险。 |
signature | HMAC-SHA256 十六进制签名。 |
message | 当 type=message 时存在。 |
callback_query | 当 type=callback_query 时存在。 |
v1 签名字符串
update_id
update_type
bot_id
timestamp
nonce
payload_sha256
- 签名算法:HMAC-SHA256。
payload_sha256 来自 message 或 callback_query 的 canonical JSON。
- canonical JSON 需要稳定排序,避免跨语言序列化差异。
timestamp 默认按 300 秒窗口做防重放校验。
Webhook 重试口径
- Webhook 是 at-least-once 投递,不是 exactly-once。
- 失败时会重试,重试应保持同一个
update_id。
- 接入方必须按
update_id 或 callback_query_id 做幂等去重。
- 响应体过大也会视为失败;当前验收口径里 10KB 以上会命中
RESPONSE_TOO_LARGE。
最常见的验签失败
| 错误 | 含义 | 优先排查 |
SIGNATURE_MISMATCH | 签名不一致。 | canonical JSON、payload_sha256、secret 是否正确。 |
REPLAY_WINDOW_EXCEEDED | 时间戳超出重放窗口。 | 服务端时间同步、请求延迟、回放样本是否过期。 |
INVALID_SIGNATURE_FORMAT | 签名不是 64 位 hex。 | 是否截断、是否误把 base64 当 hex。 |
RESPONSE_TOO_LARGE | 接收方返回体过大。 | Webhook handler 是否输出了大对象或调试日志。 |
幂等与去重
- 对 Update 消费侧,以
update_id 做去重。
- 对发送侧,以
client_msg_id 做幂等控制。
- 不要假设 Webhook 或 Polling 只会收到一次同一事件。
群聊默认规则
- 群聊默认是
mention_only,只有 @Bot 或指定命令才会投递。
- 群里多 Bot 场景,不应默认广播给所有 Bot。
- 群监听和更高风险能力是否放开,受认证和治理策略控制。
开发者最容易忽略的协议细节
| 问题 | 正确理解 |
| Webhook 和 Polling 能否同时作为主链路 | 通常不建议。启用 Webhook 后,Polling 可能拿不到更新。 |
| 群里给 Bot 发普通消息为什么不触发 | 默认是 mention_only,不是系统故障。 |
| 签名是否能用解析后的 JSON 再计算 | 不建议。应使用统一 canonical JSON 规则。 |
| 收到同一 Update 两次是不是平台 bug | 不是。接入方必须做去重。 |
parse_mode 是否已是稳定主路径 | 还不是。当前建议开发者直接显式提供 data.entities。 |