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

Backend2026-10-24TryQuickToolBox

你已经构建了一个 API,现在需要在事件发生时通知其他服务。你设置了一个 webhook 端点,但很快意识到可靠地传递 HTTP 请求比看起来要困难得多。网络故障、恶意行为者和重复事件都可能造成混乱。本指南解释了如何通过签名负载和重试机制来实现 webhooks,从而构建一个健壮的集成。

什么是 Webhooks?

Webhooks 是用户定义的 HTTP 回调。当你的应用程序中发生事件时,你会向订阅者提供的 URL 发送一个 HTTP POST 请求。这允许服务之间进行实时通信,而无需轮询。

例如,支付处理器可能会在扣款成功时向你的应用发送一个 webhook。然后你的应用可以立即更新订单状态。

为什么要对 Webhook 负载进行签名?

Webhook 端点是公开可访问的,因此任何人都可以发送虚假请求。如果没有验证,你的应用可能会处理欺诈性事件。对负载进行签名可以确保请求源自你的服务,并且未被篡改。

签名通常使用发送方和接收方之间共享的密钥。发送方基于负载计算一个基于哈希的消息认证码(HMAC),并将其包含在头部中。接收方重新计算 HMAC 并进行比较。

如何生成和验证签名

大多数实现使用 HMAC-SHA256。以下是一个 Python 中的简单示例:

import hmac
import hashlib
import json

def generate_signature(payload, secret):
    body = json.dumps(payload, separators=(',', ':')).encode('utf-8')
    signature = hmac.new(secret.encode('utf-8'), body, hashlib.sha256).hexdigest()
    return signature

def verify_signature(payload, signature, secret):
    expected = generate_signature(payload, secret)
    return hmac.compare_digest(expected, signature)

发送 webhook 时,将签名包含在类似 X-Signature 的头部中。接收方提取原始请求体,重新计算 HMAC,并进行比较。始终使用恒定时间比较函数以防止时序攻击。

实现重试以确保可靠交付

Webhooks 通过互联网发送,因此失败是不可避免的。你的服务器可能宕机、网络可能缓慢,或者订阅者可能返回错误。带指数退避的重试至关重要。

重试策略

一种常见的方法是使用递增的延迟进行重试:1 分钟、5 分钟、30 分钟、2 小时等。在达到最大尝试次数(例如 10 次)或总时间窗口(例如 24 小时)后停止。

仅在特定条件下重试:网络错误、5xx 响应或 429(请求过多)。不要对 4xx 错误(429 除外)进行重试,因为它们表示客户端错误,重试无法修复。

重试逻辑示例

以下是使用 requests 库的 Python 中的简化重试机制:

import requests
import time
from requests.exceptions import RequestException

def send_webhook(url, payload, secret, max_retries=5):
    headers = {'Content-Type': 'application/json'}
    signature = generate_signature(payload, secret)
    headers['X-Signature'] = signature
    
    for attempt in range(max_retries):
        try:
            response = requests.post(url, json=payload, headers=headers, timeout=10)
            if response.status_code < 500 and response.status_code != 429:
                return response.status_code
        except RequestException:
            pass
        
        # 带抖动的指数退避
        delay = (2 ** attempt) + (random.random() * 0.1)
        time.sleep(delay)
    
    return None  # 重试后失败

在生产环境中,你会将失败的尝试存储在队列中并异步处理。Celery 或云队列(AWS SQS、Google Cloud Tasks)等工具可以提供帮助。

Webhook 安全最佳实践

处理重复和幂等性

重试可能导致重复交付。在负载中包含唯一的事件 ID,并让接收方跟踪已处理的 ID。如果之前见过某个事件 ID,则忽略它。

或者,将事件处理程序设计为幂等的。例如,多次使用相同数据更新记录应与执行一次具有相同的效果。

测试 Webhooks

使用 ngrok 等工具暴露你的本地服务器并测试 webhook 交付。通过返回错误代码模拟失败,并验证重试按预期发生。

常见问题

什么是签名的 webhook 负载?

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

我应该重试失败的 webhook 多少次?

这取决于你的可靠性需求。常见的做法是在 24 小时内进行 5-10 次带指数退避的重试。始终允许订阅者手动触发重试。

我可以不使用签名来使用 webhooks 吗?

可以,但风险很高。没有签名,任何人都可以发送虚假事件。在生产环境中始终对 webhooks 进行签名以防止欺诈。

准备好测试你的 webhook 负载了吗?在发送之前,使用我们的 JSON 格式化工具 来验证和美化 JSON 负载。