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'}),但由于全局中间件执行顺序优先,请求体已经被解析,导致签名验证无法通过。
修复方案
- 移除全局的
bodyParser.json()中间件,避免所有请求都被提前解析。 - 给需要解析JSON的路由(比如Telegram Webhook的
URI路由)单独添加bodyParser.json()中间件。 - 确保
/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
相关产品推荐
相关产品推荐

