Webhook 接收端设计实战:验签、重试与幂等的三道防线
Webhook幂等API 签名数据集成监控告警
为什么接收端是集成的第一道防线
在订单同步、退款通知、库存变更等场景中,电商平台或 OMS(如旺店通、聚水潭)会主动把业务事件推送到你登记的回调地址。这个地址暴露在公网上,天然面临三类风险:
- 伪造请求:任何人拿到 URL 都可以 POST 一条假订单,直接污染你的 ERP 数据;
- 重复投递:平台在收不到 2xx 响应时会重试,同一事件可能被推送多次;
- 消息丢失:接收端处理超时或宕机,平台重试次数用尽后事件被丢弃。
一个生产可用的 Webhook 接收端,必须同时解决这三个问题。
第一道防线:验签
验签的目标是确认消息确实来自平台、且内容未被篡改。业界通行做法是 HMAC 签名:平台用共享密钥对**原始请求体(raw body)**计算 HMAC-SHA256,放在请求头中;接收端用同一密钥重算并比对。GitHub 的 X-Hub-Signature-256 头(格式为 sha256=<hex>)就是典型实现。
ts
import crypto from "node:crypto";
function verify(rawBody: Buffer, signature: string, secret: string): boolean {
const expected =
"sha256=" + crypto.createHmac("sha256", secret).update(rawBody).digest("hex");
// 必须常数时间比较,避免时序攻击
return crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(signature));
}
两个容易踩的坑:
- 必须用 raw body 计算签名。如果框架(如 Express/Next.js)已经把 body 解析成 JSON 再重新序列化,键顺序和空白变化会导致签名对不上;
- 比对要用常数时间函数,不能用
===,否则存在时序侧信道。
部分国内电商 ERP 采用 MD5 签名(参数排序拼接后加密钥再 MD5),原理相同,只是算法不同,接入时以平台文档为准。
第二道防线:快速 ACK + 异步处理
平台普遍要求接收端在 3~5 秒内返回成功,否则判定投递失败并重试。因此不要在接收请求里同步跑业务逻辑。正确姿势是:
- 验签通过后,把原始事件连同事件 ID 落库(或写入消息队列);
- 立即返回 200;
- 由后台 worker 异步消费,失败可重试、可告警。
这样即使下游 ERP 抖动,也只是队列积压,不会触发平台侧的重复投递风暴。
第三道防线:幂等消费
无论平台重试还是 worker 重试,同一事件都可能被消费多次。消费端必须以平台下发的事件 ID / 单据号作为幂等键:处理前先查去重表,已处理则直接丢弃;写入业务表时以单据号做唯一约束兜底。
落地清单
| 环节 | 必做项 |
|---|---|
| 入口 | HTTPS、验签(raw body + 常数时间比较)、时间戳防重放(±5 分钟) |
| 接收 | 3 秒内 ACK,事件先落库/入队再处理 |
| 消费 | 以事件 ID 幂等,失败指数退避重试,超过上限进死信队列 |
| 可观测 | 记录每个事件的接收时间、验签结果、消费状态,异常触发告警 |
在轻易云平台上,上述三层能力(验签配置、事件暂存、幂等写入)均已内置于 Webhook 触发器,接入方只需配置密钥与字段映射即可上线。
本文为原创内容,转载请注明出处:/insights/engineering/webhook-receiver-design