
什么是 Webhook?
Webhook 是一种 HTTP 回调——当事件发生时,另一个服务会向你的 URL 发送 POST 请求。你无需反复询问“有变化吗?”(轮询),服务会立即通知你。
轮询: 你的应用 → API 每 60 秒: "有新支付吗?" API: "没有... 没有... 有!"
Webhook: 支付发生 → Stripe 立即 POST 到 your-app.com/webhooks
Webhook 是实时的、高效的(没有浪费的请求),并且比轮询更容易扩展。

Webhook 工作原理
- 你在服务中注册一个 URL:
https://your-app.com/webhooks/stripe - 事件发生(支付、用户注册等)
- 服务向你的 URL 发送 POST 请求,包含 JSON 负载
- 你的服务器处理事件并返回 HTTP 200
- 如果你没有返回 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));
});

手动验证签名
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 服务超时时间为 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 签名。