Webhooks 详解:签名负载与重试策略

Backend2026-10-19TryQuickToolBox

你刚刚集成了一个会发送 webhooks 的第三方服务,测试时一切正常。但在生产环境中,你偶尔会发现事件丢失、重复投递,更糟的是——有恶意攻击者向你的端点发送伪造负载。Webhooks 概念简单但实践起来却很棘手。本文介绍如何通过签名负载保护 webhooks 安全,并实现稳健的重试策略,让你能信任事件驱动的集成。

什么是 Webhooks 及其重要性

Webhooks 是由特定事件触发的用户定义 HTTP 回调。当事件发生时——比如支付成功或新用户注册——源服务会向你提供的 URL 发送 HTTP POST 请求。这种基于推送的模式比轮询更高效,能降低延迟和服务器负载。然而,它也带来了挑战:验证真实性和处理投递失败。

使用签名负载保护 Webhooks

如果不进行验证,任何人都可以向你的 webhook 端点发送请求。签名负载通过包含一个加密签名来解决这个问题,证明请求来自可信发送方。

HMAC 签名的工作原理

大多数提供商使用 HMAC(基于哈希的消息认证码)和共享密钥。发送方使用密钥计算负载的哈希值,并将其包含在请求头中(例如 X-Signature)。你的服务器重新计算哈希值并进行比较。如果匹配,则请求是真实的。

// 示例:在 Node.js 中验证 webhook 签名
const crypto = require('crypto');

function verifySignature(payload, signature, secret) {
  const hmac = crypto.createHmac('sha256', secret);
  hmac.update(payload);
  const digest = hmac.digest('hex');
  return crypto.timingSafeEqual(Buffer.from(signature), Buffer.from(digest));
}

重要: 始终使用时间安全的比较以防止时序攻击。同时,针对原始请求体验证签名,而不是解析后的 JSON 对象,因为解析可能会改变负载。

签名验证的最佳实践

实现可靠的重试

即使有签名负载,网络问题或临时中断也可能导致 webhook 投递失败。稳健的重试策略确保事件最终被投递,而不会压垮你的系统。

带抖动的指数退避

立即重试可能会加剧拥塞。相反,使用指数退避:每次重试之间等待更长时间。添加抖动(随机变化)以避免许多 webhooks 同时失败时的惊群问题。

// 示例:带指数退避和抖动的重试计划
function getRetryDelay(attempt) {
  const base = 1000; // 1 秒
  const max = 30000; // 30 秒
  const delay = Math.min(base * Math.pow(2, attempt), max);
  return delay + Math.random() * 1000; // 添加最多 1 秒抖动
}

幂等性:处理重复

重试可能导致重复投递。你的 webhook 处理器必须是幂等的——多次处理同一事件应与处理一次具有相同的效果。使用负载中的事件 ID 来跟踪已处理的事件。

快速响应并确认

你的 webhook 端点应尽快返回 2xx 状态码——理想情况下在几秒内。如果处理时间较长,将事件排队进行异步处理并返回 202 Accepted。这可以防止超时和不必要的重试。

比较:Webhook 与轮询

方面 Webhooks 轮询
延迟 近实时 取决于间隔
服务器负载 低(仅推送) 高(持续请求)
复杂性 较高(需要重试、安全) 较低
可靠性 需要重试逻辑 固有可靠

分步指南:构建安全的 Webhook 接收器

  1. 设置 HTTPS 端点,使用有效证书。
  2. 提取签名头(例如 X-Signature)和时间戳。
  3. 验证签名,使用共享密钥和原始请求体。
  4. 检查时间戳,拒绝旧请求(例如超过 5 分钟)。
  5. 解析负载并检查重复的事件 ID。
  6. 异步处理事件(如果需要),并返回 200 OK。
  7. 记录事件用于审计和调试。

常见问题

什么是签名的 webhook 负载?

签名负载包含一个加密签名(通常是 HMAC),允许接收方验证请求来自可信来源且未被篡改。

失败的 webhook 应该重试多少次?

大多数系统使用指数退避重试 3–5 次。之后,事件通常被标记为失败,可能需要手动干预或死信队列处理。

可以在没有 HTTPS 的情况下使用 webhooks 吗?

强烈不建议。没有 HTTPS,负载和签名可能被拦截或修改。始终为 webhook 端点使用 TLS。

在调试 webhook 负载或日志时,正确格式化 JSON 至关重要。使用我们的 JSON 格式化工具 快速美化并验证 JSON 负载。