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

Express中Stripe Webhook签名验证失败问题求助

Stripe Webhook签名验证失败问题排查修复

问题描述

我正在使用Express集成Stripe Webhook,却出现「Stripe Webhook签名验证失败」错误。我知道这与bodyparser有关,但找不到解决办法,以下是错误信息及源代码,恳请帮忙排查修复。

错误信息

Webhook signature verification failed. No signatures found matching the expected signature for payload. Are you passing the raw request body you received from Stripe? https://github.com/stripe/stripe-node#webhook-signing

源代码

require("dotenv").config();
const express = require("express");
var cors = require('cors')
const axios = require("axios");
const bodyParser = require("body-parser");
const Stripe = require('stripe');
const stripe = Stripe('sk_test_4eC39HqLyjWDarjtT1zdp7dc'); 
const invoiceDescription = require('./constants');

const endpointSecret = "whsec_w5Qipi3Wz0fm8sSCHcJIHwWfobS0kfYe";

const { TOKEN, SERVER_URL, BOTTOKEN } = process.env;
const TELEGRAM_API = `https://api.telegram.org/bot${TOKEN}`;
const URI = `/webhook/${TOKEN}`;
const WEBHOOK_URL = SERVER_URL + URI;

const app = express();
app.use(cors());
app.use(bodyParser.json());


const init = async () => {
  const res = await axios.get(`${TELEGRAM_API}/setWebhook?url=${WEBHOOK_URL}`);
  console.log(res.data);
};

app.post('/webhook', express.raw({type: 'application/json'}), (request, response) => {
    let event = request.body;
    // Only verify the event if you have an endpoint secret defined.
    // Otherwise use the basic event deserialized with JSON.parse
    if (endpointSecret) {
      // Get the signature sent by Stripe
      console.log(request.headers)
      const signature = request.headers['stripe-signature'];
      try {
        event = stripe.webhooks.constructEvent(
          request.body,
          signature,
          endpointSecret
        );
      } catch (err) {
        console.log(`⚠️  Webhook signature verification failed.`, err.message);
        return response.sendStatus(400);
      }
    }
  
    // Handle the event
    switch (event.type) {
      case 'payment_intent.succeeded':
        const paymentIntent = event.data.object;
        console.log(`PaymentIntent for ${paymentIntent.amount} was successful!`);
        // Then define and call a method to handle the successful payment intent.
        // handlePaymentIntentSucceeded(paymentIntent);
        console.log('payment intent is', paymentIntent)
        break;
      case 'payment_method.attached':
        const paymentMethod = event.data.object;
        // Then define and call a method to handle the successful attachment of a PaymentMethod.
        // handlePaymentMethodAttached(paymentMethod);
        console.log('payment method is', paymentMethod)
        break;
      default:
        // Unexpected event type
        console.log(`Unhandled event type ${event.type}.`);
    }
  
    // Return a 200 response to acknowledge receipt of the event
    response.send();
  });

app.post(URI, async (req, res) => {
  let text = "", chatId = "", userObjectForTable = {}; 
  if(req.body.message?.chat?.id && req.body.message?.text && req.body.message?.text === "Start"){
        chatId = req.body.message.chat.id;
        text = invoiceDescription;
        const message = await axios.post(`${TELEGRAM_API}/sendMessage`, {
            chat_id: chatId,
            text: text,
            reply_markup: {
                inline_keyboard: [[{
                    text: 'Pay $65.00',
                    web_app: {url: 'https://buy.stripe.com/test_14kbKj3Gd0AGeRi7ss' }
                }]]
            }
        });
        
    }
  return res.send();
});

app.listen(process.env.PORT || 5050, async () => {
  console.log("🚀 app running on port", process.env.PORT || 5050);
  await init();
});

问题根源

全局使用的bodyParser.json()会提前将所有POST请求的body解析为JSON对象,而Stripe Webhook签名验证必须使用原始未解析的请求体(Buffer格式)。尽管你给/webhook路由配置了express.raw({type: 'application/json'}),但由于全局中间件执行顺序优先,请求体已经被解析,导致签名验证无法通过。

修复方案

  1. 移除全局的bodyParser.json()中间件,避免所有请求都被提前解析。
  2. 给需要解析JSON的路由(比如Telegram Webhook的URI路由)单独添加bodyParser.json()中间件。
  3. 确保/webhook路由的express.raw中间件优先执行,拿到原始请求体。

修改后的代码

require("dotenv").config();
const express = require("express");
var cors = require('cors')
const axios = require("axios");
const bodyParser = require("body-parser");
const Stripe = require('stripe');
const stripe = Stripe('sk_test_4eC39HqLyjWDarjtT1zdp7dc'); 
const invoiceDescription = require('./constants');

const endpointSecret = "whsec_w5Qipi3Wz0fm8sSCHcJIHwWfobS0kfYe";

const { TOKEN, SERVER_URL, BOTTOKEN } = process.env;
const TELEGRAM_API = `https://api.telegram.org/bot${TOKEN}`;
const URI = `/webhook/${TOKEN}`;
const WEBHOOK_URL = SERVER_URL + URI;

const app = express();
app.use(cors());
// 移除全局的bodyParser.json()

const init = async () => {
  const res = await axios.get(`${TELEGRAM_API}/setWebhook?url=${WEBHOOK_URL}`);
  console.log(res.data);
};

// Stripe Webhook路由,优先使用raw中间件获取原始请求体
app.post('/webhook', express.raw({type: 'application/json'}), (request, response) => {
    let event = request.body;
    if (endpointSecret) {
      const signature = request.headers['stripe-signature'];
      try {
        event = stripe.webhooks.constructEvent(
          request.body,
          signature,
          endpointSecret
        );
      } catch (err) {
        console.log(`⚠️  Webhook signature verification failed.`, err.message);
        return response.sendStatus(400);
      }
    }
  
    switch (event.type) {
      case 'payment_intent.succeeded':
        const paymentIntent = event.data.object;
        console.log(`PaymentIntent for ${paymentIntent.amount} was successful!`);
        console.log('payment intent is', paymentIntent)
        break;
      case 'payment_method.attached':
        const paymentMethod = event.data.object;
        console.log('payment method is', paymentMethod)
        break;
      default:
        console.log(`Unhandled event type ${event.type}.`);
    }
  
    response.send();
  });

// Telegram Webhook路由,单独添加JSON解析中间件
app.post(URI, bodyParser.json(), async (req, res) => {
  let text = "", chatId = "", userObjectForTable = {}; 
  if(req.body.message?.chat?.id && req.body.message?.text && req.body.message?.text === "Start"){
        chatId = req.body.message.chat.id;
        text = invoiceDescription;
        const message = await axios.post(`${TELEGRAM_API}/sendMessage`, {
            chat_id: chatId,
            text: text,
            reply_markup: {
                inline_keyboard: [[{
                    text: 'Pay $65.00',
                    web_app: {url: 'https://buy.stripe.com/test_14kbKj3Gd0AGeRi7ss' }
                }]]
            }
        });
        
    }
  return res.send();
});

app.listen(process.env.PORT || 5050, async () => {
  console.log("🚀 app running on port", process.env.PORT || 5050);
  await init();
});

额外检查项

  • 确认Stripe控制台中配置的Webhook签名密钥与代码中的endpointSecret完全一致,无拼写或格式错误。
  • 测试时使用Stripe CLI发送测试事件,确保请求体未被代理或其他中间件修改。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.22 10:15:50