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

Backend2026-10-27TryQuickToolBox

你刚刚集成了支付提供商的 webhook 来接收交易更新。测试中一切正常,但在生产环境中,你发现在流量高峰时事件丢失,或者当提供商重试时出现重复扣款。Webhook 概念简单但实践起来很棘手:它们跨越网络边界,可能被延迟、重复或欺骗。本文解释如何使用签名负载和智能重试策略构建健壮的 webhook 消费者和生产者。

什么是 Webhooks?

Webhooks 是由事件触发的用户定义的 HTTP 回调。当源系统发生事件(例如新订单)时,它会向您提供的 URL 发送 HTTP POST 请求。与轮询不同,webhook 近实时推送数据,减少延迟和服务器负载。

然而,webhook 带来了挑战:确保请求确实来自预期的发送方、优雅地处理失败以及避免重复处理。

使用签名负载保护 Webhooks

如果没有验证,任何人都可以向您的 webhook 端点发送虚假请求。签名负载通过包含一个您可以验证的加密签名来解决这个问题。

HMAC 签名的工作原理

发送方使用共享密钥和请求体计算基于哈希的消息认证码(HMAC)。签名在头部发送(例如 X-Signature)。您在本地重新计算 HMAC 并进行比较。

// Node.js 示例:验证 webhook 签名
const crypto = require('crypto');

function verifySignature(payload, signature, secret) {
  const expected = crypto
    .createHmac('sha256', secret)
    .update(payload)
    .digest('hex');
  return crypto.timingSafeEqual(
    Buffer.from(signature),
    Buffer.from(expected)
  );
}

始终使用像 timingSafeEqual 这样的恒定时间比较函数来防止时序攻击。

包含时间戳

为了防止重放攻击,在签名负载中包含时间戳,并拒绝超过几分钟(例如 5 分钟)的请求。时间戳应该是签名数据的一部分。

实现可靠的重试

网络问题或临时服务器错误可能导致 webhook 传递失败。健壮的发送方会使用指数退避重试失败的传递。

重试策略

示例:Python 中带退避的重试

import time
import requests
from requests.exceptions import RequestException

def send_webhook(url, payload, max_retries=5):
    for attempt in range(max_retries):
        try:
            response = requests.post(url, json=payload, timeout=5)
            if response.status_code < 500:
                return True  # 成功或客户端错误(不重试)
        except RequestException:
            pass
        time.sleep(2 ** attempt)  # 指数退避
    return False

Webhook 消费者的最佳实践

比较:Webhooks 与轮询

方面Webhooks轮询
延迟近实时取决于间隔
服务器负载低(仅推送)高(频繁请求)
复杂性较高(需要端点、安全)较低
可靠性需要重试固有重试

常见问题

什么是签名 webhook 负载?

签名负载包含一个加密签名(通常是 HMAC),允许接收方验证请求的真实性和完整性。

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

常见的做法是使用指数退避进行 3-5 次重试,但这取决于您的 SLA 和事件的关键性。

我可以不使用 HTTPS 使用 webhooks 吗?

不可以,始终使用 HTTPS 来保护负载和签名不被拦截。纯 HTTP 会暴露敏感数据。

在调试 webhook 负载时,您可能需要快速格式化 JSON。试试我们的 JSON 格式化工具 来美化和验证 JSON 响应。