部署在Vercel的Next.js项目Stripe webhook端点请求被拒、签名校验失败怎么办?
问题原因
- 环境/配置不匹配:本地用的是Stripe CLI生成的测试签名密钥,生产环境需要用Stripe后台对应Webhook生成的独立签名密钥,两者不一致会直接导致校验失败;另外如果Stripe后台填写的Webhook地址是HTTP协议,Vercel会自动重定向到HTTPS,跳转过程会丢失原始请求体和签名头。
- 请求流被提前消费:如果项目配置了全局中间件
middleware.ts,且中间件内有读取请求体的操作,会导致API路由拿到的请求流为空;部分CORS、请求解析类的中间件也会自动解析并修改原始请求体,破坏签名所需的原始内容。 - 平台差异:Vercel Serverless Function的运行环境和本地开发环境的请求处理逻辑存在差异,手动读取请求流的逻辑在部分场景下会拿到异常内容。
解决方案
- 校验基础配置
- 登录Stripe后台,打开生产环境Webhook配置页,复制Signing secret值,确认Vercel项目环境变量中存储的webhookSecret和该值完全一致,修改环境变量后需要重新部署项目生效。
- 确认Stripe后台填写的Webhook地址是完整的HTTPS地址,格式为
https://你的部署域名/api/stripe/webhooks,避免触发Vercel的自动跳转。
- 移除不必要的中间件
Stripe Webhook调用属于服务器端请求,不会触发浏览器CORS预检,直接删除现有CORS配置即可,避免CORS库修改原始请求体。同时检查全局中间件middleware.ts,删除所有消费请求体的逻辑(比如request.json()、request.text()调用)。 - 适配Vercel的原始请求体获取逻辑
Vercel会自动将未解析的原始请求体挂载到req.rawBody属性上,优先使用该属性获取原始内容,同时兼容本地开发环境的手动buffer逻辑,修改后的代码如下:const webhookHandler = async (req: NextApiRequest, res: NextApiResponse) => { if (req.method === 'POST') { // 优先读取Vercel注入的原始body, fallback到手动读取适配本地环境 const rawBody = (req.rawBody as Buffer) ?? await buffer(req) const sig = req.headers['stripe-signature'] as string let event: Stripe.Event try { event = stripe.webhooks.constructEvent(rawBody.toString(), sig, webhookSecret) } catch (err) { console.log(`❌ Error message: ${err.message}`) res.status(400).send(`Webhook Error: ${err.message}`) return } // 保留原有业务逻辑 } }
内容的提问来源于stack exchange,提问作者Jele
相关产品推荐
相关产品推荐

