部署在Vercel的Express应用Stripe Webhook签名验证失败求助
问题复现
本地通过stripe-cli测试Stripe Webhook完全正常,但部署到Vercel后触发错误:
No signatures found matching the expected signature for payload. Are you passing the raw request body you received from Stripe?
If a webhook request is being forwarded by a third-party tool, ensure that the exact request body, including JSON formatting and new line style, is preserved.
核心原因
Vercel的默认配置或中间件顺序问题,导致Stripe Webhook的原始请求体被修改(比如提前解析为JSON),破坏了签名验证所需的原始payload。
解决方案
1. 调整中间件顺序,避免全局JSON解析影响Webhook
原代码中通过路径判断跳过JSON解析的方式在Vercel上可能因路由匹配逻辑失效,改为先挂载Webhook路由,再全局应用JSON解析:
修改app.js:
// 先引入并挂载Stripe Webhook路由 const stripeRouter = require('./path/to/stripe.route.js'); app.use('/api/stripe', stripeRouter); // 再给其他路由全局应用JSON解析 app.use(express.json());
删除原有的条件判断中间件,这样Webhook请求会直接进入express.raw中间件处理,确保拿到原始请求体。
2. 配置Vercel禁止自动解析请求体
Vercel的Node.js函数默认会自动解析请求体,需通过vercel.json禁用该行为:
在项目根目录创建/修改vercel.json:
{ "functions": { "api/**/*.js": { "runtime": "@vercel/node@20", "config": { "bodyParser": false } } } }
该配置指定所有api目录下的Node函数不自动解析请求体,让Express的中间件自行处理。
3. 验证环境变量正确性
确认Vercel上配置的stripe.endpointSecret与Stripe Dashboard中Webhook设置的签名密钥完全一致(注意区分测试环境和生产环境的密钥)。
4. 排查请求体格式(可选)
在stripe.controller.js中添加日志,验证请求体是否为原始Buffer类型:
const stripeWebhook = async (req, res) => { // 打印请求体信息用于排查 console.log('请求体类型:', typeof req.body); console.log('请求体长度:', req.body.length); const sig = req.headers['stripe-signature']; let event; try { event = stripe.webhooks.constructEvent(req.body, sig, envVars.stripe.endpointSecret); } catch (err) { console.log(err.message) res.status(400).send(`Webhook Error: ${err.message}`); return; } // ... 后续逻辑 }
如果Vercel日志中显示请求体为object或string,说明仍被提前解析,需重新检查中间件顺序和Vercel配置。
内容的提问来源于stack exchange,提问作者Orest Stryhunov

