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

Backend2026-11-02TryQuickToolBox

为什么 Webhooks 需要签名和重试

你已经构建了一个端点来接收来自支付提供商或 SaaS 平台的 Webhooks。在测试中一切正常,但在生产环境中你面临两个问题:如何知道请求确实来自提供商,以及当服务器宕机或响应缓慢时会发生什么?没有签名载荷,任何人都可以伪造请求。没有重试机制,一次网络波动就可能导致事件永久丢失。

本指南解释如何正确实现两者。我们将涵盖 HMAC 签名、验证步骤、重试策略和幂等性——可靠处理 Webhooks 的三大支柱。

Webhook 签名的工作原理

Webhook 签名是一种加密哈希,用于证明载荷是由知道共享密钥的人创建的。大多数提供商使用 HMAC-SHA256。发送方对原始请求体(有时包含时间戳)计算签名,并将其包含在请求头中。接收方重新计算并比较。

以下是典型流程:

  1. 提供商和消费者共享一个密钥(在提供商的控制面板中配置)。
  2. 提供商创建载荷,计算 HMAC-SHA256(secret, payload),并将其放在类似 X-Signature 的请求头中发送。
  3. 消费者接收请求,读取原始请求体,重新计算 HMAC,并使用恒定时间函数进行比较。
  4. 如果匹配,则处理事件;否则,返回 401 拒绝。

重要: 始终使用接收到的原始请求体,而不是解析后重新序列化的版本。JSON 序列化可能会改变空白和键的顺序,从而破坏签名。

添加时间戳以防止重放攻击

仅凭签名载荷,攻击者可以捕获有效请求并重放。为了缓解,在签名数据中包含时间戳,并拒绝超过几分钟的请求。例如:

signed_payload = timestamp + "." + raw_body
signature = HMAC-SHA256(secret, signed_payload)

消费者检查时间戳是否在可接受的时间窗口内(例如 5 分钟),并且签名匹配。

实现签名验证

让我们看一个使用 Flask 的 Python 具体示例。我们将验证 X-Signature 请求头中发送的签名。

import hmac
import hashlib
from flask import Flask, request, abort

app = Flask(__name__)
SECRET = b'your_shared_secret'

@app.route('/webhook', methods=['POST'])
def webhook():
    signature = request.headers.get('X-Signature')
    if not signature:
        abort(401)

    raw_body = request.get_data()
    expected = hmac.new(SECRET, raw_body, hashlib.sha256).hexdigest()

    if not hmac.compare_digest(expected, signature):
        abort(401)

    # 处理事件
    event = request.json
    # ... 处理事件 ...
    return '', 200

关键点:

设计重试策略

即使有签名,Webhooks 也可能失败:你的服务器可能宕机、返回 500 或超时。提供商通常使用指数退避进行重试。作为消费者,你也应该优雅地处理重试。

以下是需要考虑的事项:

大多数提供商在非 2xx 响应或超时时重试,通常持续长达 24 小时,间隔逐渐增加。请查看提供商文档了解具体细节。

示例:幂等事件处理器

使用数据库表跟踪已处理的事件 ID。以下是一个简化模式:

def handle_event(event_id, payload):
    if db.exists('processed_events', event_id):
        return  # 已处理
    # 处理载荷
    db.insert('processed_events', event_id)
    # ... 实际工作 ...

对于高吞吐量,在事件 ID 上使用唯一约束并捕获重复键错误。

比较:签名与未签名 Webhooks

方面签名载荷未签名载荷
安全性验证发送方真实性任何人都可以伪造请求
重放保护可通过时间戳实现无
实现工作量中等(HMAC + 时间戳检查)低
推荐用于所有生产 Webhooks仅内部测试

常见陷阱与最佳实践

常见问题

什么是签名 Webhook 载荷?

签名载荷包含加密签名(通常是 HMAC),让接收方验证请求来自可信来源且未被篡改。

如何在不重复操作的情况下处理 Webhook 重试?

通过跟踪事件 ID 使处理器幂等。在处理之前,检查 ID 是否已处理;如果是,则返回成功而不重复工作。

我可以使用没有签名的 Webhooks 吗?

不建议用于生产环境。没有签名,你无法区分合法请求和伪造请求。当可用时,始终验证签名。

在调试 Webhook 载荷或分析服务器日志时,JSON Formatter 可以帮助你快速检查和验证 JSON 结构。