轻易云
注册体验

Webhook 接收端设计实战:验签、重试与幂等的三道防线

· 系统管理员· 工程最佳实践· 4 次浏览· 约 2 分钟读完
Webhook幂等API 签名数据集成监控告警

为什么接收端是集成的第一道防线

在订单同步、退款通知、库存变更等场景中,电商平台或 OMS(如旺店通、聚水潭)会主动把业务事件推送到你登记的回调地址。这个地址暴露在公网上,天然面临三类风险:

  1. 伪造请求:任何人拿到 URL 都可以 POST 一条假订单,直接污染你的 ERP 数据;
  2. 重复投递:平台在收不到 2xx 响应时会重试,同一事件可能被推送多次;
  3. 消息丢失:接收端处理超时或宕机,平台重试次数用尽后事件被丢弃。

一个生产可用的 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 秒内返回成功,否则判定投递失败并重试。因此不要在接收请求里同步跑业务逻辑。正确姿势是:

  1. 验签通过后,把原始事件连同事件 ID 落库(或写入消息队列);
  2. 立即返回 200;
  3. 由后台 worker 异步消费,失败可重试、可告警。

这样即使下游 ERP 抖动,也只是队列积压,不会触发平台侧的重复投递风暴。

第三道防线:幂等消费

无论平台重试还是 worker 重试,同一事件都可能被消费多次。消费端必须以平台下发的事件 ID / 单据号作为幂等键:处理前先查去重表,已处理则直接丢弃;写入业务表时以单据号做唯一约束兜底。

落地清单

环节必做项
入口HTTPS、验签(raw body + 常数时间比较)、时间戳防重放(±5 分钟)
接收3 秒内 ACK,事件先落库/入队再处理
消费以事件 ID 幂等,失败指数退避重试,超过上限进死信队列
可观测记录每个事件的接收时间、验签结果、消费状态,异常触发告警

在轻易云平台上,上述三层能力(验签配置、事件暂存、幂等写入)均已内置于 Webhook 触发器,接入方只需配置密钥与字段映射即可上线。

本文为原创内容,转载请注明出处:/insights/engineering/webhook-receiver-design

评论