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

Backend2026-09-18TryQuickToolBox

你刚刚集成了支付网关或 CI/CD 服务,现在需要接收实时事件。Webhook 是标准解决方案,但它们也伴随着一些陷阱:未认证的负载、丢失的事件以及重复传递。本文将解释 webhook 的工作原理、如何对负载进行签名以防止篡改,以及如何实现可靠传递的重试机制。

什么是 Webhook?

Webhook 是一种用户定义的 HTTP 回调。当事件发生时,提供方会向你指定的 URL 发送 HTTP POST 请求,而不是让你的应用程序轮询 API 获取更新。这种方式更高效,并能实现近乎实时的响应。

常见用例包括:

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

Webhook 端点是可公开访问的 URL。如果没有验证,任何人都可以发送虚假负载,导致数据损坏或安全漏洞。使用共享密钥对负载进行签名可以确保真实性和完整性。

大多数提供方使用 HMAC(基于哈希的消息认证码)和 SHA-256。提供方使用密钥对原始请求体计算签名,并将其包含在请求头中(例如 X-Hub-Signature-256)。你的服务器重新计算签名并进行比较。

如何验证签名

以下是分步指南:

  1. 获取原始请求体,确保与发送时完全一致。在验证之前不要解析或修改它。
  2. 从请求头中提取签名。
  3. 使用你的密钥通过 HMAC-SHA256 计算预期的签名。
  4. 使用恒定时间函数进行比较,以防止时序攻击。

Node.js 示例:

const crypto = require('crypto');

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

Python 示例:

import hmac
import hashlib

def verify_signature(payload, signature, secret):
    expected = hmac.new(secret.encode(), payload, hashlib.sha256).hexdigest()
    return hmac.compare_digest(f'sha256={expected}', signature)

始终使用恒定时间比较,以避免泄露信息。

实现重试以确保可靠性

网络会故障,服务器会重启,部署也会发生。一个健壮的 webhook 系统必须重试失败的传递。提供方通常使用指数退避进行重试,但你也应该在接收端处理重试。

重试策略

幂等键

许多提供方包含唯一的事件 ID(例如 X-Event-ID)。将已处理的 ID 存储在数据库或缓存中,以跳过重复项。例如,使用带有 TTL 的 Redis 来跟踪已见过的 ID。

比较:轮询 vs Webhook

方面 轮询 Webhook
延迟 取决于间隔 近乎实时
服务器负载 高(持续请求) 低(仅在事件发生时)
复杂性 实现简单 需要端点、安全性和重试
可靠性 在轮询间隔之间可能遗漏事件 如果端点宕机可能遗漏;重试有助于恢复

Webhook 使用者的最佳实践

常见问题解答

什么是 webhook 签名?

Webhook 签名是使用共享密钥创建的负载的 HMAC 哈希。它允许接收方验证请求来自预期的发送方,并且未被篡改。

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

没有统一的标准,但通常使用指数退避进行 3–5 次重试。之后,将事件记录到死信队列以供手动审查。

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

技术上可以,但非常不安全。始终使用 HTTPS 来加密负载并防止中间人攻击。

准备好测试你的 webhook 端点了吗?使用我们的 JSON Formatter 快速检查和验证负载。