正在加载,请稍候…

什么是 Webhook?Webhook 工作原理及如何构建可靠的处理程序

了解什么是 webhook、它与轮询的区别、如何安全接收和验证 webhook 负载,以及如何处理重试和幂等性。

什么是 Webhook?Webhook 工作原理及如何构建可靠的处理程序

什么是 Webhook?

Webhook 是一种 HTTP 回调——当事件发生时,另一个服务会向你的 URL 发送 POST 请求。你无需反复询问“有变化吗?”(轮询),服务会立即通知你。

轮询:   你的应用 → API 每 60 秒: "有新支付吗?"  API: "没有... 没有... 有!"
Webhook: 支付发生 → Stripe 立即 POST 到 your-app.com/webhooks

Webhook 是实时的、高效的(没有浪费的请求),并且比轮询更容易扩展。

什么是 Webhook?Webhook 工作原理及如何构建可靠的处理程序 插图

Webhook 工作原理

  1. 你在服务中注册一个 URL:https://your-app.com/webhooks/stripe
  2. 事件发生(支付、用户注册等)
  3. 服务向你的 URL 发送 POST 请求,包含 JSON 负载
  4. 你的服务器处理事件并返回 HTTP 200
  5. 如果你没有返回 2xx,服务会重试(指数退避)

接收 Webhook(Express)

// 重要:使用 express.raw() 而不是 express.json() —— 需要原始 body 进行签名验证
app.post('/webhooks/stripe', express.raw({ type: 'application/json' }), async (req, res) => {
  // 步骤 1:首先验证签名
  let event;
  try {
    event = stripe.webhooks.constructEvent(
      req.body,
      req.headers['stripe-signature'],
      process.env.STRIPE_WEBHOOK_SECRET
    );
  } catch (err) {
    return res.status(400).send('签名验证失败');
  }

  // 步骤 2:立即确认
  res.json({ received: true });

  // 步骤 3:异步处理(在响应之后)
  setImmediate(() => processEvent(event));
});

什么是 Webhook?Webhook 工作原理及如何构建可靠的处理程序 插图

手动验证签名

const crypto = require('crypto');

function verifyWebhook(payload, signature, secret) {
  const expected = crypto.createHmac('sha256', secret).update(payload).digest('hex');
  const sigBuffer = Buffer.from(signature, 'hex');
  const expectedBuffer = Buffer.from(expected, 'hex');
  if (sigBuffer.length !== expectedBuffer.length) return false;
  return crypto.timingSafeEqual(sigBuffer, expectedBuffer);  // 防止时序攻击
}

// GitHub:'X-Hub-Signature-256' 头部格式为 'sha256=...'
app.post('/webhooks/github', express.raw({ type: 'application/json' }), (req, res) => {
  const sig = req.headers['x-hub-signature-256'].replace('sha256=', '');
  if (!verifyWebhook(req.body, sig, process.env.GITHUB_SECRET)) {
    return res.status(401).send('未授权');
  }
  // 处理...
});

处理重复事件(幂等性)

服务在失败时会重试——你的处理程序可能多次收到同一事件。务必去重:

async function processPayment(paymentIntent) {
  // 检查是否已处理
  const existing = await db.query(
    'SELECT id FROM orders WHERE payment_id = $1',
    [paymentIntent.id]
  );
  if (existing.rowCount > 0) return;  // 已处理,跳过

  // 首次处理
  await db.query('INSERT INTO orders ...', [paymentIntent.id, ...]);
}

什么是 Webhook?Webhook 工作原理及如何构建可靠的处理程序 插图

快速响应,稍后处理

Webhook 服务超时时间为 5-30 秒。对于慢速处理,先确认并将工作加入队列:

app.post('/webhooks/stripe', express.raw({ type: 'application/json' }), async (req, res) => {
  const event = verifyAndParse(req);
  await queue.add('process-webhook', { event });  // 推入任务队列
  res.json({ received: true });  // 立即响应
});

本地测试

# 使用 ngrok 暴露本地服务
ngrok http 3000
# → https://abc123.ngrok.io/webhooks/stripe

# Stripe CLI:将真实事件转发到本地服务器
stripe listen --forward-to localhost:3000/webhooks/stripe
stripe trigger payment_intent.succeeded

签名头部参考

服务 头部 算法
Stripe Stripe-Signature HMAC-SHA256 + 时间戳
GitHub X-Hub-Signature-256 HMAC-SHA256
Shopify X-Shopify-Hmac-Sha256 HMAC-SHA256 (base64)
Slack X-Slack-Signature HMAC-SHA256 + 时间戳

→ 使用 HMAC 生成器 生成和验证 HMAC 签名。