Webhooks 详解:签名负载与重试机制
你刚刚集成了支付网关或 CI/CD 服务,现在需要接收实时事件。Webhook 是标准解决方案,但它们也伴随着一些陷阱:未认证的负载、丢失的事件以及重复传递。本文将解释 webhook 的工作原理、如何对负载进行签名以防止篡改,以及如何实现可靠传递的重试机制。
什么是 Webhook?
Webhook 是一种用户定义的 HTTP 回调。当事件发生时,提供方会向你指定的 URL 发送 HTTP POST 请求,而不是让你的应用程序轮询 API 获取更新。这种方式更高效,并能实现近乎实时的响应。
常见用例包括:
- 支付通知(例如 Stripe、PayPal)
- CI/CD 构建状态(例如 GitHub、GitLab)
- 消息平台(例如 Slack、Discord)
- CRM 更新(例如 Salesforce)
为什么需要对 Webhook 负载进行签名?
Webhook 端点是可公开访问的 URL。如果没有验证,任何人都可以发送虚假负载,导致数据损坏或安全漏洞。使用共享密钥对负载进行签名可以确保真实性和完整性。
大多数提供方使用 HMAC(基于哈希的消息认证码)和 SHA-256。提供方使用密钥对原始请求体计算签名,并将其包含在请求头中(例如 X-Hub-Signature-256)。你的服务器重新计算签名并进行比较。
如何验证签名
以下是分步指南:
- 获取原始请求体,确保与发送时完全一致。在验证之前不要解析或修改它。
- 从请求头中提取签名。
- 使用你的密钥通过 HMAC-SHA256 计算预期的签名。
- 使用恒定时间函数进行比较,以防止时序攻击。
Node.js 示例:
const crypto = require('crypto');
function verifySignature(payload, signature, secret) {
const hmac = crypto.createHmac('sha256', secret);
const digest = 'sha256=' + hmac.update(payload).digest('hex');
return crypto.timingSafeEqual(Buffer.from(digest), Buffer.from(signature));
}
Python 示例:
import hmac
import hashlib
def verify_signature(payload, signature, secret):
expected = hmac.new(secret.encode(), payload, hashlib.sha256).hexdigest()
return hmac.compare_digest(f'sha256={expected}', signature)
始终使用恒定时间比较,以避免泄露信息。
实现重试以确保可靠性
网络会故障,服务器会重启,部署也会发生。一个健壮的 webhook 系统必须重试失败的传递。提供方通常使用指数退避进行重试,但你也应该在接收端处理重试。
重试策略
- 指数退避: 在重试之间等待 1 秒、2 秒、4 秒、8 秒等。
- 最大尝试次数: 限制重试次数以避免无限循环(例如 5 次尝试)。
- 死信队列: 达到最大尝试次数后,存储事件以供手动检查。
- 幂等性: 确保多次处理同一事件不会导致重复的副作用。
幂等键
许多提供方包含唯一的事件 ID(例如 X-Event-ID)。将已处理的 ID 存储在数据库或缓存中,以跳过重复项。例如,使用带有 TTL 的 Redis 来跟踪已见过的 ID。
比较:轮询 vs Webhook
| 方面 | 轮询 | Webhook |
|---|---|---|
| 延迟 | 取决于间隔 | 近乎实时 |
| 服务器负载 | 高(持续请求) | 低(仅在事件发生时) |
| 复杂性 | 实现简单 | 需要端点、安全性和重试 |
| 可靠性 | 在轮询间隔之间可能遗漏事件 | 如果端点宕机可能遗漏;重试有助于恢复 |
Webhook 使用者的最佳实践
- 快速响应: 在几秒内返回 2xx;异步处理。
- 验证签名: 始终在处理前进行验证。
- 记录所有内容: 保留原始负载和请求头以供调试。
- 使用 HTTPS: 绝不接受通过纯 HTTP 传输的 webhook。
- 监控失败: 为重复重试失败设置警报。
常见问题解答
什么是 webhook 签名?
Webhook 签名是使用共享密钥创建的负载的 HMAC 哈希。它允许接收方验证请求来自预期的发送方,并且未被篡改。
失败的 webhook 应该重试多少次?
没有统一的标准,但通常使用指数退避进行 3–5 次重试。之后,将事件记录到死信队列以供手动审查。
我可以不使用 HTTPS 使用 webhook 吗?
技术上可以,但非常不安全。始终使用 HTTPS 来加密负载并防止中间人攻击。
准备好测试你的 webhook 端点了吗?使用我们的 JSON Formatter 快速检查和验证负载。