You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

NextJS中Stripe Webhook验证失败(400错误)求助

排查Stripe Webhook验证400错误及正确实现方案

核心问题排查点

  • NextJS自动解析body导致签名验证失败:NextJS API路由默认会把请求body解析成JSON,但Stripe签名验证必须用原始未解析的请求体,解析后必然验证失败返回400。
  • stripe-signature header获取错误:注意请求头的大小写(部分代理会转成小写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');
}

测试验证步骤

  1. 用Stripe CLI发送测试事件:
    stripe listen --forward-to localhost:3000/api/webhook
    stripe trigger payment_intent.succeeded
    
  2. 查看控制台日志,确认是否有具体的签名错误提示
  3. 核对Stripe CLI输出的webhook signing secret和代码里的STRIPE_WEBHOOK_SECRET是否一致
  4. 打印rawBody确认是否拿到了原始的请求体内容

内容的提问来源于stack exchange,提问作者seven

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.26 05:24:27