Webhook 详解:签名载荷与重试逻辑
你已经构建了一个 API,现在需要在事件发生时通知其他服务。轮询效率低下,所以你转向了 Webhook。但很快你会发现 Webhook 可能会失败、被伪造或导致重复处理。本文介绍如何通过签名载荷保护 Webhook,并实现健壮的重试逻辑。
什么是 Webhook?
Webhook 是用户定义的 HTTP 回调。当你的系统中发生事件(例如支付成功)时,你向订阅者提供的 URL 发送 HTTP POST 请求。载荷通常包含 JSON 或表单格式的事件数据。
Webhook 使服务之间能够实时通信,而无需持续轮询。它们被支付网关、CI/CD 工具和 SaaS 平台广泛使用。
为什么需要保护 Webhook?
如果没有安全措施,任何知道 Webhook URL 的人都可以发送虚假请求,导致数据损坏或欺诈。此外,攻击者可能重放旧请求。使用共享密钥对载荷进行签名可确保真实性和完整性。
使用 HMAC 对载荷进行签名
HMAC(基于哈希的消息认证码)是对 Webhook 进行签名的常用方法。发送方和接收方共享一个密钥。发送方使用密钥计算载荷的 HMAC,并将其包含在头部(例如 X-Signature)中。接收方重新计算 HMAC 并进行比较。
分步指南:发送签名 Webhook
- 创建载荷(JSON)。
- 使用密钥计算载荷的 HMAC-SHA256。
- 将十六进制摘要包含在头部中,例如
X-Signature: sha256=...。 - 发送 POST 请求。
// 使用 crypto 的 Node.js 示例
const crypto = require('crypto');
const secret = 'your_shared_secret';
const payload = JSON.stringify({ event: 'payment.success', amount: 100 });
const signature = crypto.createHmac('sha256', secret).update(payload).digest('hex');
// 发送 POST 请求,头部包含 X-Signature: sha256=signature
验证签名
在接收方,提取签名头部,计算原始请求体的 HMAC,并使用恒定时间比较来防止时序攻击。
// Express.js 示例
app.post('/webhook', (req, res) => {
const signature = req.headers['x-signature'];
const computed = crypto.createHmac('sha256', secret).update(req.rawBody).digest('hex');
if (!crypto.timingSafeEqual(Buffer.from(signature), Buffer.from(computed))) {
return res.status(401).send('Invalid signature');
}
// 处理载荷
res.status(200).send('OK');
});
注意:确保使用原始请求体,而不是解析后的 JSON,因为解析可能会改变载荷。
防止重放攻击
在签名载荷中包含时间戳,并拒绝超过几分钟的请求。同时,包含唯一的事件 ID 并存储已处理的 ID 以避免重复。
可靠交付的重试逻辑
Webhook 端点可能宕机或响应缓慢。实现带有指数退避和抖动的重试。设置最大重试次数(例如 5 次)和每个请求的超时时间。
重试策略
- 指数退避:等待 1 秒、2 秒、4 秒、8 秒等。
- 抖动:添加随机性以避免惊群效应。
- 幂等性:使用幂等键,使重复交付不会导致重复操作。
示例重试计划:第 1 次重试在 5 秒后,第 2 次在 30 秒后,第 3 次在 2 分钟后,第 4 次在 10 分钟后,第 5 次在 1 小时后。
处理失败
如果所有重试都失败,记录失败并通知订阅者(例如通过电子邮件或仪表板)。提供手动重新发送 Webhook 的方式。
比较:签名载荷 vs. 基本认证
| 方法 | 安全性 | 复杂性 | 重放保护 |
|---|---|---|---|
| HMAC 签名载荷 | 高 | 中等 | 是(带时间戳) |
| 基本认证 | 低 | 低 | 否 |
| 头部中的 API 密钥 | 中 | 低 | 否 |
最佳实践
- 始终对 Webhook 端点使用 HTTPS。
- 定期轮换密钥。
- 在处理前验证载荷模式。
- 快速响应(2xx 状态码)并异步处理。
- 监控 Webhook 交付指标(成功率、延迟)。
常见问题
如何验证 Webhook 签名?
使用你的密钥计算原始请求体的 HMAC,并使用恒定时间比较将其与签名头部进行比较。
如果 Webhook 失败,我该怎么办?
实现带有指数退避和抖动的重试。如果所有重试都失败,记录失败并提供手动重新发送选项。
我可以不使用签名而使用 Webhook 吗?
不建议这样做。没有签名,你无法验证发送方,使你的系统容易受到欺骗和重放攻击。
在测试 Webhook 载荷时,你可能需要检查 JSON 响应。使用我们的 JSON Formatter 快速美化打印和验证 JSON 数据。