Stripe Webhook集成问题:原始请求体导致签名验证失败
问题
在基于Firebase Cloud Functions运行的Node/Express.ts后端中集成Stripe Webhook时,签名验证环节无法正确传递原始请求体,持续报错:
Webhook Error: Webhook payload must be provided as a string or a Buffer instance representing the raw request body. Payload was provided as a parsed JavaScript object instead. Signature verification is impossible without access to the original signed material.
相关代码如下:
index.ts
const app = express(); app.use(cors()); app.use(allowedOriginsHandler); app.post("/api/v1/stripe/webhook/payment", express.raw({ type: '*/*' }), listenToPaymentEvents); app.use(express.json()); app.use("/api/v1", routes); app.use(errorLogger); app.use(errorResponder); app.use(failSafeHandler); export const httpFunction = functions.https.onRequest(app); //admin.initializeApp(); //MARKETPLACE CLOUD FUNCTIONS //orders export const onOrderDocumentCreateFunction = onOrderDocumentCreate; export const onOrderStatusChangeFunction = onOrderStatusChange; //check order products availability export const onOrderByVendorCreateFunction = onCreateOrderByVendor; //products (create/update subcollections) export const onProductDocumentCreateFunction = onCreateProduct; export const onProductDocumentUpdateFunction = onUpdateProduct; //Update seller public info collection on seller create/update export const onSellerCreateFunction = onCreateSeller; export const onSellerUpdateFunction = onUpdateSeller; export const onUserRegistrationFunction = onUserRegistration; // Create seller products bulk upload template on seller create // and update seller document with the template url info export const createSellerBulkUploadTemplateFunction = createSellerBulkUploadTemplate;
路由配置
const router = Router(); router.route("/newsletter").post(addContactToNewsletter); router.route("/checkout").post( firebaseAuthenticationHandler, checkProductsAvailabilityHandler, checkProductsPriceChangesHandler, lockProductsHandler, checkout ); router.route("/order/vendor/cancel/:id").put( firebaseAuthenticationHandler, orderCancelledByVendor ); router.route("/order/buyer/cancel").post(orderCancelledByBuyer); router.route("/order/vendor/processing/:id").put( firebaseAuthenticationHandler, updateOrderToProcessing ); export default router;
Webhook控制器
dotenv.config(); const stripePaymentIntentWebhookEndpointSecret = process.env.STRIPE_PAYMENT_INTENT_WEBHOOK_ENDPOINT_SECRET ?? ""; export const listenToPaymentEvents: RequestHandler = async (req, res) => { const sig = req.headers["stripe-signature"]; if (!sig) { console.log("Missing Stripe signature"); res.status(400).send("Missing Stripe signature"); return; } let event; try { event = stripe.webhooks.constructEvent( req.body, sig, stripePaymentIntentWebhookEndpointSecret ); switch (event.type) { case "payment_intent.payment_failed": { const paymentIntentPaymentFailed = event.data.object; console.log(paymentIntentPaymentFailed); break; } case "payment_intent.succeeded": { const paymentIntentSucceeded = event.data.object; await handlePaymentIntentSuccess(paymentIntentSucceeded); break; } default: { console.log(`Unhandled event type ${event.type}`); } } res.status(200).send("Event received"); } catch (error) { if (error instanceof Error) { res.status(500).send(`Webhook Error: ${error.message}`); } else { res.status(500).send("Webhook Error: An unexpected error occurred"); } } };
解决方案
问题核心是Firebase Cloud Functions的默认行为会自动解析请求体,导致Express的express.raw中间件无法拿到原始数据,具体修复步骤如下:
- 禁用Firebase自动解析请求体
修改httpFunction的创建配置,开启rawBody选项,让Firebase保留原始请求体:
export const httpFunction = functions.https.onRequest({ rawBody: true }, app);
- 调整Webhook控制器的请求体来源
Firebase会将原始请求体存储在req.rawBody上,因此需要修改Stripe事件构造代码,将req.body替换为req.rawBody:
event = stripe.webhooks.constructEvent( req.rawBody, // 使用原始请求体而非解析后的对象 sig, stripePaymentIntentWebhookEndpointSecret );
- 移除冗余的Express中间件
由于已经通过Firebase获取原始请求体,Webhook路由上的express.raw({ type: '*/*' })中间件可以移除,简化路由配置:
app.post("/api/v1/stripe/webhook/payment", listenToPaymentEvents);
- 确认中间件顺序
确保express.json()中间件在Webhook路由之后加载,避免其他路由的JSON解析逻辑影响Webhook请求(当前代码顺序已符合要求,无需额外调整)。
内容的提问来源于stack exchange,提问作者Aurora
相关产品推荐
相关产品推荐

