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

本地环境使用Stripe Webhooks遇签名验证错误,请求返回400求助

问题:Stripe Webhook签名验证失败(无stripe-signature header)

调用/webhook端点时返回以下错误响应:

{
    "type": "StripeSignatureVerificationError",
    "raw": {
        "message": "No stripe-signature header value was provided."
    },
    "payload": {}
}

代码实现

stripe.service.ts

import config from 'config';
import Stripe from 'stripe';

const stripeSecretKey = config.get<string>('stripeSecretKey');

export const stripe = new Stripe(stripeSecretKey, {
  apiVersion: '2023-10-16',
  typescript: true,
});

webhook.controller.ts

import config from 'config';
import { Request, Response, NextFunction } from 'express';
import { stripe } from '@/services/stripe.service';

const stripeWebhookSigningSecret = config.get<string>('stripeWebhookSigningSecret');

export const webhookController = async (
  req: Request,
  res: Response,
  next: NextFunction
) => {
  const payload = req.body;
  const sig = req.headers['stripe-signature'] as string;

  let event;

  try {
    event = stripe.webhooks.constructEvent(
      payload,
      sig,
      stripeWebhookSigningSecret
    );
  } catch (error) {
    return res.status(400).send(error);
  }
  console.log(event.type);
  console.log(event.data.object);
  return res.json({ received: true });
};

routes.ts

app.post(
'/webhook',
bodyParser.raw({ type: 'application/json' }),
webhookController
);

CLI操作流程

- npm run dev
> API is running on http://localhost:3000.

- stripe login

- stripe listen --forward-to localhost:3000/webhook
> Ready! You are using Stripe API Version [2022-11-15]. Your webhook signing secret is whsec_xxx

2023-10-26 23:43:26   --> charge.succeeded [evt_xxx]
2023-10-26 23:43:26  <--  [400] POST http://localhost:3000/webhook [evt_xxx]
2023-10-26 23:43:26   --> payment_intent.succeeded [evt_xxx]
2023-10-26 23:43:26  <--  [400] POST http://localhost:3000/webhook [evt_xxx]
2023-10-26 23:43:26   --> payment_intent.created [evt_xxx]
2023-10-26 23:43:26  <--  [400] POST http://localhost:3000/webhook [evt_xxx]

- stripe trigger payment_intent.succeeded
> Setting up fixture for: payment_intent
  Running fixture for: payment_intent
  Trigger succeeded! Check dashboard for event details.

解决方案

1. 确认签名密钥完全匹配

复制stripe listen输出的whsec_xxx签名密钥,确保它和你配置的stripeWebhookSigningSecret完全一致,不要包含多余空格或字符。

2. 排查Express中间件冲突

如果你的应用全局使用了bodyParser.json(),必须确保webhook路由的bodyParser.raw()优先级更高:

  • 要么将webhook路由放在全局bodyParser.json()中间件之前
  • 要么在全局中间件中排除/webhook路径,示例:
    app.use((req, res, next) => {
      if (req.path === '/webhook') {
        return next();
      }
      bodyParser.json()(req, res, next);
    });
    

因为一旦JSON解析器先处理请求,req.body会被转换成JSON对象,而非原始的Buffer/字符串,导致签名验证失败。

3. 统一Stripe API版本

Stripe CLI当前使用的API版本是2022-11-15,但代码中指定的是2023-10-16,虽然这不是直接导致签名缺失的原因,但版本不一致可能引发其他问题,建议统一:

stripe listen --forward-to localhost:3000/webhook --api-version 2023-10-16

4. 验证请求头是否被拦截

在webhook控制器中添加日志,确认stripe-signature头是否存在:

export const webhookController = async (req: Request, res: Response, next: NextFunction) => {
  // 先打印所有请求头
  console.log('Request headers:', req.headers);
  // 后续代码不变...
};

如果头不存在,检查是否有反向代理(如Nginx)或其他中间件移除了该请求头。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.07 11:43:25