Webhook 推送(M7-01)
当主机的 L1 配置发生变更(无论从哪个入口改),中枢会主动 POST到主机配置的 report_url。
适用场景:
- 你的 SaaS 平台需要在主机 secret 变更后立即同步自己的密钥库
- 多方系统协同管控同一台主机,需要事件通知
- 审计/合规要求保留外部记录
1. 请求格式
POST {report_url}
Content-Type: application/json
User-Agent: gateway-new-webhook/1.0
X-Gateway-Event: host.l1_config.updated
X-Gateway-Delivery: wd_01HXX...
X-Gateway-Timestamp: 1736832000
X-Gateway-Signature: sha256=<hex>
{
"event": "host.l1_config.updated",
"delivery_id": "wd_01HXX...",
"timestamp": "2026-05-14T08:30:00Z",
"host_id": "uuid-of-host",
"host_name": "dev-pc-01",
"changed_by": {
"kind": "user",
"id": "uuid-of-user",
"ip": "203.0.113.5"
},
"before": {
"bid": 1001,
"report_url": "https://old.example.com/cb"
},
"after": {
"bid": 8848,
"secret_key": "0123456789abcdef0123456789abcdef",
"report_url": "https://your-business.example.com/gateway/report"
}
}
注意:
before不含旧secret_key(已用于签名),after含新的secret_key明文, 接收方应在通道安全(HTTPS)的前提下立即落库到自己的密钥本。
2. 签名校验
中枢用旧的 secret_key 对 X-Gateway-Timestamp + "." + body 做 HMAC-SHA256,
hex 编码后写入 X-Gateway-Signature。
为什么用旧 secret_key?
你的系统在收到此 webhook 之前还不知道新 secret,但本地有旧值——用旧值签名意味着只有”知道旧 secret 的中枢”才能发出有效消息,防伪造。验证通过后,再把 after.secret_key 更新到本地。
Python 校验示例
import hmac, hashlib
def verify(old_secret: str, timestamp: str, body: bytes, signature: str) -> bool:
msg = timestamp.encode() + b"." + body
expect = "sha256=" + hmac.new(old_secret.encode(), msg, hashlib.sha256).hexdigest()
return hmac.compare_digest(expect, signature)
Go 校验示例
import (
"crypto/hmac"
"crypto/sha256"
"encoding/hex"
)
func Verify(oldSecret, timestamp string, body []byte, signature string) bool {
mac := hmac.New(sha256.New, []byte(oldSecret))
mac.Write([]byte(timestamp))
mac.Write([]byte("."))
mac.Write(body)
expect := "sha256=" + hex.EncodeToString(mac.Sum(nil))
return hmac.Equal([]byte(expect), []byte(signature))
}
3. 重试策略
接收方需要在 5 秒内返回 HTTP 2xx,否则视为失败。
失败后按指数退避重试:
| 第 N 次 | 延迟 |
|---|---|
| 1 | 立即 |
| 2 | 30s |
| 3 | 2min |
| 4 | 10min |
| 5 | 1h |
5 次全失败后进入死信队列(webhook_deliveries.status = 'dead'),不再自动重试。
用户可在 控制台 → 主机详情 → Webhook 历史 看到失败原因并手动 retry。
4. 幂等性建议
中枢可能因网络重传同一个 delivery_id 多次,接收方应按 delivery_id 去重:
INSERT INTO webhook_inbox (delivery_id, event, payload)
VALUES ($1, $2, $3)
ON CONFLICT (delivery_id) DO NOTHING;
返回 200 即可,不要返回 4xx(会触发重试浪费资源)。
5. 查询投递历史
用户接口:
GET /v1/user/hosts/{host_id}/webhook-deliveries?limit=20
Authorization: Bearer ak_<key_id>.<secret>
返回最近 N 次投递记录:状态码、响应体摘要、首发/末次重试时间、是否落入死信。
6. 时间戳防重放
X-Gateway-Timestamp 是 Unix 秒。接收方建议拒绝 |now - timestamp| > 300s 的请求,
即使签名合法也不接受 —— 防止重放老旧投递。