Webhooks 详解:签名载荷与重试机制
你刚刚使用 webhooks 集成了一款支付网关。测试中一切正常,但在生产环境中,事件有时会丢失,或者收到重复通知导致重复扣款。根本原因往往在于如何处理签名载荷和重试。本文解释 webhooks 的工作原理,并提供构建健壮、安全集成的实用步骤。
什么是 Webhooks?
Webhooks 是用户定义的 HTTP 回调。当源系统发生事件(例如处理付款)时,它会向您指定的 URL 发送包含事件数据的 HTTP POST 请求。与轮询不同,webhooks 近实时推送数据,减少延迟和服务器负载。
然而,webhooks 也带来挑战:验证真实性、处理失败以及确保恰好一次处理。让我们逐一解决。
签名载荷:验证真实性
由于 webhook 端点是公开可访问的,任何人都可能发送伪造请求。为了防止这种情况,提供商用密钥对载荷进行签名。您验证签名以确保请求是真实的。
HMAC 签名如何工作
大多数提供商使用 HMAC(基于哈希的消息认证码)和 SHA-256。提供商使用请求体和共享密钥计算签名,然后将其包含在标头中(例如 X-Signature)。您在本地重新计算签名并进行比较。
Python 示例:
import hmac
import hashlib
def verify_signature(payload, secret, received_signature):
expected = hmac.new(secret.encode(), payload, hashlib.sha256).hexdigest()
return hmac.compare_digest(expected, received_signature)
重要:使用原始请求体,而不是解析后的 JSON,因为解析可能会改变空白并破坏签名。
常见陷阱
- 使用解析后的数据:始终针对原始字节进行验证。
- 时序攻击:使用恒定时间比较(例如
hmac.compare_digest)。 - 忽略时间戳:一些提供商在签名中包含时间戳以防止重放攻击。验证其在容差窗口内(例如 5 分钟)。
重试:优雅处理失败
您的端点可能暂时宕机,或网络问题可能导致失败。可靠的 webhook 系统会使用指数退避重试失败的传递。
重试策略
提供商通常在非 2xx 响应或超时时重试。常见模式:
| 策略 | 描述 | 示例 |
|---|---|---|
| 固定间隔 | 每 N 秒重试一次 | 每 30 秒,最多 5 次 |
| 指数退避 | 每次尝试延迟加倍 | 1s, 2s, 4s, 8s... |
| 带抖动的指数退避 | 添加随机性以避免惊群效应 | 1s ± 0.5s, 2s ± 1s... |
作为接收方,您无法控制发送方的重试策略,但可以设计端点使其具有弹性。
接收方最佳实践
- 快速响应:在几秒内返回 2xx。将处理卸载到后台任务。
- 幂等:使用载荷中的幂等键避免重复处理。
- 记录一切:存储传入的 webhooks 用于调试和重放。
- 监控失败:为重复失败设置警报。
实现幂等性
重复会发生:发送方可能因为您的响应缓慢而重试,或者您可能意外处理同一事件两次。幂等性确保多次处理事件与一次处理具有相同效果。
使用唯一事件 ID(通常在载荷或标头中提供),并将其存储在具有唯一约束的数据库中。在处理之前,检查 ID 是否存在;如果存在,则跳过。
def process_event(event_id, data):
if EventLog.exists(event_id):
return # 已处理
EventLog.create(event_id)
# 处理数据...
对于高吞吐量系统,使用分布式锁或数据库事务以避免竞态条件。
安全考虑
除了签名验证,还要考虑:
- HTTPS:始终使用 TLS 防止窃听。
- IP 允许列表:如果提供商发布 IP 范围,限制传入请求。
- 速率限制:保护端点免受滥用。
- 载荷验证:即使签名验证后,也要验证数据模式以避免注入攻击。
本地测试 Webhooks
在开发过程中,您需要一个公共 URL 来接收 webhooks。ngrok 或 localtunnel 等工具暴露您的本地服务器。或者,许多提供商提供 CLI 将事件转发到您的 localhost。
模拟失败以测试重试处理:故意返回 500 错误并观察提供商的重试模式。
常见问题
如何验证 webhook 签名?
使用共享密钥的 HMAC。计算原始请求体的哈希,并使用恒定时间比较函数将其与签名标头进行比较。
如果收到重复的 webhooks 该怎么办?
通过存储事件 ID 实现幂等性。在执行业务逻辑之前检查事件是否已处理。
仅依赖 webhook 重试就能保证可靠性吗?
不能。重试有帮助,但不能保证传递。对于关键事件,将 webhooks 与轮询回退或持久化事件的消息队列结合使用。
调试 webhook 载荷时,您可能需要检查 JSON 数据。使用我们的 JSON Formatter 快速美化打印和验证载荷。