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

Backend2026-11-10TryQuickToolBox

为什么 Webhook 会失败以及如何修复

你刚刚集成了支付提供商的 Webhook 来更新应用中的订单状态。在测试中一切正常,但在生产环境中,有些事件从未到达,更糟糕的是,你收到了重复的事件。如果没有适当的保护措施,Webhook 可能会成为静默失败和安全漏洞的来源。

Webhook 是由事件触发的 HTTP 回调,但与典型的 API 调用不同,它们是推送式且异步的。这引入了两个核心挑战:验证真实性和确保交付。在本指南中,我们将通过签名负载和智能重试来解决这两个问题。

什么是 Webhook?

Webhook 是用户定义的 HTTP 回调。当源系统(例如新支付)中发生事件时,它会向您提供的 URL 发送一个包含事件数据的 HTTP POST 请求。这允许实时反应而无需轮询。

常见用例包括支付通知、CI/CD 触发器和聊天集成。但由于 Webhook 穿越公共互联网,它们容易受到欺骗和丢失的影响。

使用签名负载保护 Webhook

你如何知道 Webhook 真正来自你的提供商?使用共享密钥对负载进行签名可确保完整性和真实性。

HMAC 签名如何工作

提供商使用密钥计算请求体的 HMAC(例如 SHA-256),并在标头中发送签名(例如 X-Signature)。你的服务器重新计算 HMAC 并使用恒定时间比较来防止时序攻击。

// 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));
}

最佳实践:

实现可靠的重试

即使有签名,网络问题或停机也可能导致 Webhook 交付失败。提供商通常使用指数退避进行重试,但你也必须在自己的端处理重试。

幂等性:安全重试的关键

重试可能导致重复处理。使用幂等键(通常包含在负载中或作为标头)确保每个事件只处理一次。存储已处理的事件 ID 并忽略重复项。

// 示例:幂等的 Webhook 处理程序
const processedEvents = new Set();

app.post('/webhook', (req, res) => {
  const eventId = req.body.id;
  if (processedEvents.has(eventId)) {
    return res.status(200).send('Already processed');
  }
  // 处理事件...
  processedEvents.add(eventId);
  res.status(200).send('OK');
});

接收方重试策略

如果你的处理失败,请返回 5xx 状态以触发提供商的重试。对于暂时性错误,你还可以实现自己的带退避的重试队列。

场景响应操作
成功200 OK不重试
验证错误400 Bad Request不重试(修复负载)
服务器错误500 Internal Server Error提供商重试
速率限制429 Too Many Requests提供商延迟后重试

监控和调试 Webhook

记录所有传入的 Webhook 请求,包括标头和负载(脱敏敏感数据)。使用 JSON Formatter 等工具美化打印负载以便于检查。设置失败交付的警报并跟踪重试次数。

常见问题

什么是签名 Webhook 负载?

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

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

大多数提供商使用指数退避重试 3-5 次。作为接收方,返回适当的状态码并让提供商处理重试。

我可以不使用 HTTPS 使用 Webhook 吗?

不可以——HTTPS 对于加密负载和防止中间人攻击至关重要。切勿通过纯 HTTP 接受 Webhook。

准备好检查和调试 Webhook 负载了吗?试试我们的免费 JSON Formatter,立即格式化和验证 JSON 数据。