NextJS中Stripe Webhook验证失败(400错误)求助
排查Stripe Webhook验证400错误及正确实现方案
核心问题排查点
- NextJS自动解析body导致签名验证失败:NextJS API路由默认会把请求body解析成JSON,但Stripe签名验证必须用原始未解析的请求体,解析后必然验证失败返回400。
stripe-signatureheader获取错误:注意请求头的大小写(部分代理会转成小写stripe-signature),NextJS里要通过req.headers['stripe-signature']正确获取。- Webhook Secret不匹配:必须用Stripe Dashboard对应Webhook端点的签名密钥(不是API密钥),密钥错了验证必失败。
正确的NextJS Webhook实现代码
import Stripe from 'stripe'; import { NextApiRequest, NextApiResponse } from 'next'; // 禁用NextJS自动解析body,确保拿到原始请求体 export const config = { api: { bodyParser: false, }, }; const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!, { apiVersion: '2024-06-20', // 替换成你使用的Stripe API版本 }); const webhookSecret = process.env.STRIPE_WEBHOOK_SECRET!; export default async function handler(req: NextApiRequest, res: NextApiResponse) { if (req.method !== 'POST') { return res.status(405).send('Method Not Allowed'); } // 读取原始请求体 const rawBody = await new Promise<string>((resolve, reject) => { let chunks: Uint8Array[] = []; req.on('data', (chunk) => chunks.push(chunk)); req.on('end', () => resolve(Buffer.concat(chunks).toString('utf8'))); req.on('error', reject); }); const signature = req.headers['stripe-signature']; if (!signature) { return res.status(400).send('Missing Stripe Signature'); } let event; try { event = stripe.webhooks.constructEvent( rawBody, signature, webhookSecret ); } catch (err) { // 标准错误返回:400状态码+具体错误信息 return res.status(400).send(`Webhook Error: ${(err as Error).message}`); } // 处理对应事件类型,示例:支付成功事件 switch (event.type) { case 'payment_intent.succeeded': const paymentIntent = event.data.object as Stripe.PaymentIntent; console.log('Payment succeeded:', paymentIntent.id); // 此处添加你的业务逻辑 break; default: console.log(`Unhandled event type: ${event.type}`); } res.status(200).json({ received: true }); }
关于catch块的正确写法
Stripe不同文档的写法差异只是格式细节,核心逻辑统一:
- 必须返回400状态码:因为是请求验证失败,属于客户端/Stripe侧的请求问题
- 要携带具体错误信息:方便排查签名不匹配、body被篡改等问题
- 绝对不能返回2xx状态码:否则Stripe会认为请求成功,重复发送相同事件
错误写法示例(避坑):
// 错误:返回200会导致Stripe重复发事件 catch (err) { res.status(200).send('Error'); } // 错误:无具体错误信息,难以定位问题 catch (err) { res.status(400).send('Bad Request'); }
测试验证步骤
- 用Stripe CLI发送测试事件:
stripe listen --forward-to localhost:3000/api/webhook stripe trigger payment_intent.succeeded - 查看控制台日志,确认是否有具体的签名错误提示
- 核对Stripe CLI输出的
webhook signing secret和代码里的STRIPE_WEBHOOK_SECRET是否一致 - 打印
rawBody确认是否拿到了原始的请求体内容
内容的提问来源于stack exchange,提问作者seven
相关产品推荐
相关产品推荐

