跳到主要内容

Webhook 设计与实现

问题

什么是 Webhook?如何安全可靠地实现 Webhook?

答案

Webhook 基本概念

Webhook 是一种基于 HTTP 回调的事件通知机制。当事件发生时,服务方主动向预先注册的 URL 发送 POST 请求。

接收 Webhook

webhook-receiver.ts
import crypto from 'crypto';
import express from 'express';

const app = express();

// 需要原始 body 做签名验证
app.use('/webhook', express.raw({ type: 'application/json' }));

app.post('/webhook/stripe', (req, res) => {
const signature = req.headers['stripe-signature'] as string;
const secret = process.env.STRIPE_WEBHOOK_SECRET!;

// 1. 签名校验(防伪造)
const expectedSig = crypto
.createHmac('sha256', secret)
.update(req.body)
.digest('hex');

if (signature !== `sha256=${expectedSig}`) {
return res.status(401).send('Invalid signature');
}

// 2. 解析事件
const event = JSON.parse(req.body.toString());

// 3. 幂等处理(防重复)
const processed = await redis.get(`webhook:${event.id}`);
if (processed) return res.status(200).send('Already processed');

// 4. 异步处理(快速返回 200)
await webhookQueue.add('process', event);
await redis.set(`webhook:${event.id}`, '1', 'EX', 86400);

res.status(200).send('OK');
});

发送 Webhook

webhook-sender.ts
interface WebhookConfig {
url: string;
secret: string;
events: string[];
}

class WebhookSender {
async send(config: WebhookConfig, event: string, data: unknown) {
const payload = JSON.stringify({ event, data, timestamp: Date.now() });

// 签名
const signature = crypto
.createHmac('sha256', config.secret)
.update(payload)
.digest('hex');

// 发送(带重试)
await this.sendWithRetry(config.url, payload, signature);
}

private async sendWithRetry(url: string, payload: string, sig: string, retries = 3) {
for (let i = 0; i < retries; i++) {
try {
const res = await fetch(url, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-Webhook-Signature': `sha256=${sig}`,
},
body: payload,
signal: AbortSignal.timeout(10000),
});

if (res.ok) return;
} catch {
// 指数退避重试
await sleep(1000 * 2 ** i);
}
}
// 所有重试失败,记录日志
logger.error(`Webhook delivery failed: ${url}`);
}
}
注意

接收 Webhook 时必须快速返回 200,耗时逻辑放入队列异步处理。否则对方可能超时后重试,导致重复处理。


常见面试问题

Q1: Webhook 和轮询有什么区别?

答案

维度Webhook轮询
方向推送(服务方主动通知)拉取(客户端定时查询)
实时性实时取决于间隔
资源消耗低(事件触发)高(无效请求多)
可靠性需要重试机制简单可靠

Q2: 如何保证 Webhook 的可靠性?

答案

  1. 签名校验:HMAC-SHA256 防止伪造
  2. 幂等处理:event ID 去重
  3. 重试机制:指数退避重试
  4. 超时控制:设置合理超时
  5. 日志记录:记录所有成功/失败的投递

相关链接