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

Backend2026-11-06TryQuickToolBox

你已经集成了支付网关或 CI/CD 服务,现在需要接收实时事件。Webhook 是标准解决方案,但它们带来两大挑战:验证请求确实来自预期的发送方,以及处理端点宕机或响应缓慢时的失败。本文通过实际代码示例,解释如何实现签名 Webhook 负载和健壮的重试逻辑。

什么是 Webhook?

Webhook 是一种用户定义的 HTTP 回调。当事件发生时,提供方会向您指定的 URL 发送 HTTP POST 请求,而不是由您的应用程序轮询 API 获取更新。这使得集成高效且近乎实时。

然而,Webhook 是基于推送的,这意味着您的端点必须可公开访问、能够处理意外负载,并应对重复或恶意请求。这就是签名和重试的用武之地。

为什么要对 Webhook 负载签名?

如果没有验证,任何知道您 Webhook URL 的人都可以发送虚假事件。使用共享密钥对负载签名,可以验证发送方并确保负载未被篡改。

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

实现 HMAC 验证

以下是使用 Express 在 Node.js 中的简单示例:

const crypto = require('crypto');
const express = require('express');
const app = express();

// Use raw body for signature verification
app.use(express.raw({ type: 'application/json' }));

app.post('/webhook', (req, res) => {
  const signature = req.headers['x-signature'];
  const secret = process.env.WEBHOOK_SECRET;
  const payload = req.body; // Buffer

  const expected = crypto
    .createHmac('sha256', secret)
    .update(payload)
    .digest('hex');

  if (!crypto.timingSafeEqual(Buffer.from(signature), Buffer.from(expected))) {
    return res.status(401).send('Invalid signature');
  }

  // Process the event
  const event = JSON.parse(payload);
  console.log('Received event:', event.type);
  res.status(200).send('OK');
});

app.listen(3000, () => console.log('Webhook server running'));

关键点:

处理重试:让 Webhook 可靠

网络会故障,服务器会崩溃,部署会导致停机。可靠的 Webhook 系统必须重试失败的传递。但简单的重试可能导致重复处理并使系统过载。

重试策略

常见的重试模式包括:

大多数提供方实现类似:在 24 小时内以指数退避重试最多 5 次。

幂等性:安全重试的关键

由于重试可能多次传递同一事件,您的端点必须是幂等的。在负载中包含唯一事件 ID,并存储已处理的 ID 以跳过重复。

使用简单内存集合的示例(生产环境中使用数据库):

const processedEvents = new Set();

app.post('/webhook', (req, res) => {
  // ... signature verification ...
  const event = JSON.parse(payload);
  if (processedEvents.has(event.id)) {
    return res.status(200).send('Already processed');
  }
  // Process event
  processedEvents.add(event.id);
  res.status(200).send('OK');
});

对于生产环境,使用在事件 ID 上具有唯一约束的数据库。

Webhook 消费者的最佳实践

重试策略比较

策略 优点 缺点
固定间隔 实现简单 可能使恢复中的系统过载
指数退避 在中断期间减少负载 如果发生瞬时故障,会延迟传递
带抖动的指数退避 防止同步重试 稍微复杂一些

常见问题

如何验证 Webhook 签名?

使用共享密钥计算原始请求体的 HMAC,并使用时间安全函数将其与签名头部进行比较。切勿使用简单的相等比较。

如果我的 Webhook 端点宕机了怎么办?

提供方应使用指数退避进行重试。确保您的端点快速返回 2xx 并异步处理事件,以最小化停机影响。

我可以不使用签名吗?

不建议。没有签名,您无法验证发送方,使您的端点容易受到虚假事件的影响。始终在可用时使用签名。

在测试 Webhook 负载时,您可能需要检查 JSON 数据。使用 JSON Formatter 快速美化打印和验证负载。