服务端与数据 / 进阶

Webhook 接收端:验签、去重和异步消费缺一不可

Webhook 是外部系统主动调用你的接口。接收端需要对原始字节验签、限制时间窗口、按事件 ID 去重并快速返回。

必须对原始请求体验签

JSON 解析后键顺序和空白可能变化,用重新序列化的对象验签会失败。路由应保留原始 Buffer,并把时间戳与请求体按供应商协议拼接。

EXAMPLE / 01Webhook
import crypto from 'node:crypto';

const signed = `${timestamp}.${rawBody.toString('utf8')}`;
const expected = crypto.createHmac('sha256', secret).update(signed).digest();
const received = Buffer.from(signature, 'hex');

const valid = received.length === expected.length &&
  crypto.timingSafeEqual(received, expected);

签名正确也可能是旧请求重放

拒绝与服务器时间相差过大的时间戳,例如超过 5 分钟。然后以供应商 eventId 建唯一索引;同一事件重复到达时返回成功,但不重复执行业务副作用。

EXAMPLE / 02Webhook
CREATE TABLE webhook_events (
  provider text NOT NULL,
  event_id text NOT NULL,
  event_type text NOT NULL,
  payload jsonb NOT NULL,
  received_at timestamptz NOT NULL DEFAULT now(),
  processed_at timestamptz,
  PRIMARY KEY (provider, event_id)
);

持久化后快速返回 2xx

在请求里完成发邮件、更新报表等全部工作,会让上游超时并不断重试。验签后先持久化事件并提交,再交给消费者处理。消费者同样要幂等,因为队列通常是至少投递一次。

保留事件状态、失败原因和重试次数,并提供按 eventId 重放的管理能力。不要依靠日志手工拼回事件。