Webhooks 详解:签名载荷与重试机制

Backend2026-10-08TryQuickToolBox

你刚刚使用 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,因为解析可能会改变空白并破坏签名。

常见陷阱

重试:优雅处理失败

您的端点可能暂时宕机,或网络问题可能导致失败。可靠的 webhook 系统会使用指数退避重试失败的传递。

重试策略

提供商通常在非 2xx 响应或超时时重试。常见模式:

策略描述示例
固定间隔每 N 秒重试一次每 30 秒,最多 5 次
指数退避每次尝试延迟加倍1s, 2s, 4s, 8s...
带抖动的指数退避添加随机性以避免惊群效应1s ± 0.5s, 2s ± 1s...

作为接收方,您无法控制发送方的重试策略,但可以设计端点使其具有弹性。

接收方最佳实践

  1. 快速响应:在几秒内返回 2xx。将处理卸载到后台任务。
  2. 幂等:使用载荷中的幂等键避免重复处理。
  3. 记录一切:存储传入的 webhooks 用于调试和重放。
  4. 监控失败:为重复失败设置警报。

实现幂等性

重复会发生:发送方可能因为您的响应缓慢而重试,或者您可能意外处理同一事件两次。幂等性确保多次处理事件与一次处理具有相同效果。

使用唯一事件 ID(通常在载荷或标头中提供),并将其存储在具有唯一约束的数据库中。在处理之前,检查 ID 是否存在;如果存在,则跳过。

def process_event(event_id, data):
    if EventLog.exists(event_id):
        return  # 已处理
    EventLog.create(event_id)
    # 处理数据...

对于高吞吐量系统,使用分布式锁或数据库事务以避免竞态条件。

安全考虑

除了签名验证,还要考虑:

本地测试 Webhooks

在开发过程中,您需要一个公共 URL 来接收 webhooks。ngrok 或 localtunnel 等工具暴露您的本地服务器。或者,许多提供商提供 CLI 将事件转发到您的 localhost。

模拟失败以测试重试处理:故意返回 500 错误并观察提供商的重试模式。

常见问题

如何验证 webhook 签名?

使用共享密钥的 HMAC。计算原始请求体的哈希,并使用恒定时间比较函数将其与签名标头进行比较。

如果收到重复的 webhooks 该怎么办?

通过存储事件 ID 实现幂等性。在执行业务逻辑之前检查事件是否已处理。

仅依赖 webhook 重试就能保证可靠性吗?

不能。重试有帮助,但不能保证传递。对于关键事件,将 webhooks 与轮询回退或持久化事件的消息队列结合使用。

调试 webhook 载荷时,您可能需要检查 JSON 数据。使用我们的 JSON Formatter 快速美化打印和验证载荷。