Stripe Webhook是接收支付状态变更、订阅更新等事件的核心机制,而签名验证是保障Webhook请求合法性的关键步骤。如果签名验证失败,Stripe发送的事件将无法被正确处理,容易引发业务逻辑异常。

常见的Stripe Webhook签名验证错误类型
签名验证错误的报错信息通常比较笼统,我们可以通过错误特征快速定位问题:
- 签名不匹配错误:提示
StripeSignatureVerificationError,通常是请求体被篡改、签名密钥配置错误或者请求体解析时机不对导致。 - 缺少签名头部错误:提示无法获取
stripe-signature头部,一般是请求头被中间件提前过滤,或者反向代理配置不当导致头部丢失。 - 时间戳过期错误:Stripe默认会校验事件时间戳,若服务器时间与Stripe服务器时间偏差过大,会触发时间戳校验失败。
中间件顺序对签名验证的影响
Stripe的签名验证需要原始的、未被修改的请求体,因为签名是基于原始请求体内容计算的。如果我们在执行签名验证之前,使用了会修改请求体的中间件(比如express.json()),就会导致原始请求体丢失,最终签名校验失败。这是很多开发者最容易踩的坑。
以Express框架为例,默认的中间件顺序是先执行全局的express.json()解析JSON请求体,再进入Webhook路由,此时req.body已经变成了解析后的对象,不再是Stripe发送的原始Buffer,签名验证自然会失败。
正确的中间件配置方案
1. 调整Webhook路由的中间件顺序
我们需要让Webhook路由跳过全局的JSON解析中间件,单独处理原始请求体的获取,再进行签名验证。以下是Node.js Express的示例:
const express = require('express');
const stripe = require('stripe')('你的Stripe密钥');
const app = express();
// 全局JSON解析中间件,排除Webhook路由
app.use(express.json({
verify: (req, res, buf) => {
// 如果是Webhook路由,把原始请求体存到req.rawBody
if (req.originalUrl === '/webhook') {
req.rawBody = buf.toString();
}
}
}));
// Webhook路由处理
app.post('/webhook', async (req, res) => {
const sig = req.headers['stripe-signature'];
const endpointSecret = '你的Webhook端点密钥';
let event;
try {
// 使用原始请求体进行签名验证
event = stripe.webhooks.constructEvent(req.rawBody, sig, endpointSecret);
} catch (err) {
console.error('签名验证失败:', err.message);
return res.status(400).send(`Webhook Error: ${err.message}`);
}
// 处理不同的事件类型
switch (event.type) {
case 'payment_intent.succeeded':
const paymentIntent = event.data.object;
console.log('支付成功,ID:', paymentIntent.id);
break;
case 'payment_intent.payment_failed':
console.log('支付失败');
break;
default:
console.log(`未处理的事件类型: ${event.type}`);
}
res.json({ received: true });
});
app.listen(3000, () => console.log('服务运行在3000端口'));
2. 单独为Webhook路由配置原始请求体解析
如果不想修改全局中间件的配置,也可以单独给Webhook路由添加原始请求体解析的中间件,示例如下:
const express = require('express');
const stripe = require('stripe')('你的Stripe密钥');
const app = express();
// 原始请求体解析中间件,仅用于Webhook路由
const rawBodyParser = (req, res, next) => {
let data = '';
req.on('data', chunk => {
data += chunk;
});
req.on('end', () => {
req.rawBody = data;
next();
});
};
// Webhook路由先使用原始请求体解析,再处理业务逻辑
app.post('/webhook', rawBodyParser, async (req, res) => {
const sig = req.headers['stripe-signature'];
const endpointSecret = '你的Webhook端点密钥';
let event;
try {
event = stripe.webhooks.constructEvent(req.rawBody, sig, endpointSecret);
} catch (err) {
console.error('签名验证失败:', err.message);
return res.status(400).send(`Webhook Error: ${err.message}`);
}
// 事件处理逻辑
res.json({ received: true });
});
// 其他路由使用全局JSON解析
app.use(express.json());
app.listen(3000);
错误排查步骤
如果遇到签名验证错误,可以按照以下步骤排查:
- 检查
stripe-signature头部是否正常传递,可在路由中打印req.headers确认。 - 确认使用的签名密钥是Webhook端点对应的密钥,不是Stripe的API密钥。
- 打印原始请求体和解析后的请求体,确认两者内容是否一致,排查是否被中间件修改。
- 检查服务器时间是否与网络时间同步,避免时间戳校验失败。
注意事项
Stripe的constructEvent方法要求传入的第一个参数必须是原始的请求体字符串,不能是解析后的对象,也不能是经过编码转换的内容,否则一定会导致签名验证失败。另外,如果使用了Nginx等反向代理,需要确认代理配置没有过滤stripe-signature头部,同时没有对请求体进行额外的修改或压缩,避免影响签名校验结果。
Stripe_Webhook签名验证中间件顺序Node.js错误解析修改时间:2026-07-21 02:42:28