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
相关产品推荐
相关产品推荐

