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

Stripe Webhook订阅触发时签名验证与Payload格式报错

Stripe Webhook 签名验证错误排查与修复

问题场景

使用Express搭建Stripe Webhook服务,尝试处理订阅创建事件时遇到两类请求体相关错误:

  • 传递Buffer数据时:未找到与Payload预期签名匹配的签名。您是否传递了从Stripe接收的原始请求体?
  • 传递JSON数据时:Webhook payload必须是表示原始请求体的字符串或Buffer实例,但Payload被提供为已解析的JavaScript对象。没有原始签名材料则无法进行签名验证。

用户提供的原始代码:

const express = require("express");
const app = express();

app.set("port", process.env.PORT);

app.post(
  "/webhook",
  express.raw({ type: "application/json" }),
  (request, response) => {
    const sig = request.headers["stripe-signature"];

    // console.log('endpointSecret', process.env.END_POINT_SECRET)
    // console.log('sig', sig)
    console.log("request.body", request.body);

    let event;

    try {
      event = stripe.webhooks.constructEvent(
        request.body,
        sig,
        process.env.END_POINT_SECRET
      );
    } catch (err) {
      console.log(`Webhook Error: ${err.message}`);
      return response.status(400).send(`Webhook Error: ${err.message}`);
    }

    // Handle the event
    let resData = "";

    switch (event.type) {
      case "payment_intent.succeeded":
        const paymentIntent = event.data.object;
        resData = `PaymentIntent for ${paymentIntent.amount} was successful!`;

        break;
      case "payment_method.attached":
        resData = event.data.object;
        // Then define and call a method to handle the successful attachment of a PaymentMethod.
        // handlePaymentMethodAttached(paymentMethod);
        break;

      case "customer.created":
        resData = event.data.object;
        break;
      case "customer.deleted":
        resData = event.data.object;
        break;
      case "customer.updated":
        resData = event.data.object;
        break;
      case "customer.subscription.created":
        resData = event.data.object;
        break;
      case "customer.subscription.trial_will_end":
        resData = event.data.object;
        break;
      case "payment_intent.succeeded":
        resData = event.data.object;
        break;
      case "customer.subscription.deleted":
        resData = event.data.object;
        break;
      default:
        console.log(`Unhandled event type ${event.type}`);
    }

    console.log(resData);

    return response.status(200).send(resData);
  }
);

// Start server
app.listen(app.get("port"), () => {
  console.log("Server listening on : ", app.get("port"));
});

错误原因与修复方案

1. 缺失Stripe模块导入

代码中未引入Stripe SDK,导致stripe对象未定义,需在代码顶部添加:

const stripe = require('stripe')(process.env.STRIPE_SECRET_KEY);

其中STRIPE_SECRET_KEY为你的Stripe秘钥,需通过环境变量获取。

2. 避免请求体被提前解析

确保express.json()这类JSON解析中间件没有在/webhook路由前全局生效,否则请求体会被提前解析为对象,无法用于签名验证。若必须全局使用JSON解析,可单独为/webhook路由跳过该中间件:

// 全局使用JSON解析,但排除webhook路由
app.use((req, res, next) => {
  if (req.path === '/webhook') return next();
  express.json()(req, res, next);
});

3. 确认签名密钥正确性

process.env.END_POINT_SECRET必须是Stripe控制台中对应Webhook端点的签名密钥,而非Stripe API密钥,可在Stripe后台的Webhook设置中获取该密钥。

4. 不修改原始请求体

禁止在中间件或路由处理函数中修改request.body,也不要对其进行额外转换,确保传递给stripe.webhooks.constructEvent的是Stripe发送的原始Buffer数据。

修复后的完整代码

const express = require("express");
const stripe = require('stripe')(process.env.STRIPE_SECRET_KEY);
const app = express();

app.set("port", process.env.PORT);

// 全局中间件:排除webhook路由的JSON解析
app.use((req, res, next) => {
  if (req.path === '/webhook') return next();
  express.json()(req, res, next);
});

app.post(
  "/webhook",
  express.raw({ type: "application/json" }),
  (request, response) => {
    const sig = request.headers["stripe-signature"];
    const endpointSecret = process.env.END_POINT_SECRET;

    let event;

    try {
      event = stripe.webhooks.constructEvent(
        request.body,
        sig,
        endpointSecret
      );
    } catch (err) {
      console.error(`Webhook Error: ${err.message}`);
      return response.status(400).send(`Webhook Error: ${err.message}`);
    }

    // 处理事件
    let resData = "";

    switch (event.type) {
      case "payment_intent.succeeded":
        const paymentIntent = event.data.object;
        resData = `PaymentIntent for ${paymentIntent.amount} was successful!`;
        break;
      case "payment_method.attached":
        resData = event.data.object;
        break;
      case "customer.created":
        resData = event.data.object;
        break;
      case "customer.deleted":
        resData = event.data.object;
        break;
      case "customer.updated":
        resData = event.data.object;
        break;
      case "customer.subscription.created":
        resData = event.data.object;
        console.log("订阅创建成功:", resData);
        break;
      case "customer.subscription.trial_will_end":
        resData = event.data.object;
        break;
      case "customer.subscription.deleted":
        resData = event.data.object;
        break;
      default:
        console.log(`未处理的事件类型: ${event.type}`);
    }

    console.log(resData);
    return response.status(200).send(resData);
  }
);

app.listen(app.get("port"), () => {
  console.log("服务器监听端口: ", app.get("port"));
});

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.27 17:17:53