Express.js Stripe Webhook路由配置正确却返回404错误求助
问题详情
概述
我正在开发一个Express.js应用以处理Stripe Webhook。尽管已按推荐配置Webhook路由,但当Stripe尝试向我的Webhook端点发送事件时,始终遇到404 Not Found错误。
环境
- Node.js版本: v20.14.0
- Express版本: 4.18.2
- Stripe版本: 1.19.5
问题描述
当我运行Stripe CLI将事件转发到本地服务器时,每个事件都会返回404 Not Found错误。错误详情:
2024-06-09 16:30:24 --> invoice.paid [evt_1PPfGkRsJfE0QDMT8OOiGWVh] 2024-06-09 16:30:24 <-- [404] POST http://localhost:3080/webhook [evt_1PPfGkRsJfE0QDMT8OOiGWVh]
服务器代码(index.js)
require('dotenv').config(); const path = require('path'); const express = require('express'); const cors = require('cors'); const mongoSanitize = require('express-mongo-sanitize'); const routes = require('./routes'); const webhookRoutes = require('./routes/webhookRoutes'); const { PORT, HOST } = process.env; const port = Number(PORT) || 3080; const host = HOST || 'localhost'; const app = express(); app.disable('x-powered-by'); app.use(cors()); app.use(express.json({ limit: '3mb' })); app.use(mongoSanitize()); app.use(express.urlencoded({ extended: true, limit: '3mb' })); // Webhook route must handle raw request bodies for signature verification app.use('/webhook', express.raw({ type: 'application/json' }), webhookRoutes); app.use('/api', routes); // Example for other routes app.use((req, res) => { res.status(404).sendFile(path.join(__dirname, 'public', '404.html')); }); app.listen(port, host, () => { console.log(`Server listening at http://${host}:${port}`); });
Webhook路由(webhookRoutes.js)
const express = require('express'); const router = express.Router(); const { handleWebhook } = require('../controllers/webhookController'); router.post('/', express.raw({ type: 'application/json' }), handleWebhook); module.exports = router;
Webhook控制器(webhookController.js)
const Stripe = require('stripe'); const { updateUserSubscriptionStatus } = require('../../models/userMethods'); const { logger } = require('~/config'); const stripe = Stripe(process.env.STRIPE_SECRET_KEY); const endpointSecret = process.env.STRIPE_ENDPOINT_SECRET; const handleWebhook = async (req, res) => { logger.info('Received a webhook event'); let event = req.body; // Verify webhook signature if (endpointSecret) { const signature = req.headers['stripe-signature']; try { event = stripe.webhooks.constructEvent(req.body, signature, endpointSecret); logger.info('Webhook signature verified'); } catch (err) { logger.error('⚠️ Webhook signature verification failed:', err.message); return res.status(400).send(`Webhook Error: ${err.message}`); } } try { switch (event.type) { case 'invoice.paid': { const invoice = event.data.object; const customerId = invoice.customer; logger.info(`Handling invoice.paid for customer ${customerId}`); const updateResult = await updateUserSubscriptionStatus(customerId, 'active'); if (updateResult instanceof Error) { logger.error('[updateUserSubscriptionStatus]', updateResult); const { status, message } = updateResult; return res.status(status).send({ message }); } logger.info('User subscription status updated successfully'); res.status(200).send('Success'); break; } case 'payment_intent.succeeded': { const paymentIntent = event.data.object; logger.info(`PaymentIntent for ${paymentIntent.amount} was successful!`); res.status(200).send('Success'); break; } case 'payment_method.attached': { const paymentMethod = event.data.object; logger.info('PaymentMethod successfully attached'); res.status(200).send('Success'); break; } default: { logger.warn(`Unhandled event type ${event.type}`); res.status(400).send(`Unhandled event type ${event.type}`); } } } catch (err) { logger.error('[handleWebhook]', err); res.status(500).json({ message: err.message }); } }; module.exports = { handleWebhook, };
复现步骤
- 启动服务器:
node index.js - 使用Stripe CLI转发事件:
stripe listen --forward-to http://localhost:3080/webhook - 触发测试事件:
stripe trigger invoice.paid
预期行为
Webhook端点应正确接收并处理Stripe事件,服务器应记录事件并返回200状态码。
实际行为
服务器针对Webhook端点返回404 Not Found错误。
排查与修复方案
1. 移除重复的请求体解析中间件
你在index.js中已经为/webhook路由配置了express.raw中间件,但在webhookRoutes.js的POST路由里又重复添加了一次。重复解析可能导致请求处理异常,修改webhookRoutes.js:
// webhookRoutes.js router.post('/', handleWebhook);
2. 验证路由匹配与请求路径
在404中间件添加日志,确认请求的实际路径和方法:
app.use((req, res) => { console.log(`404 received: ${req.method} ${req.path}`); res.status(404).sendFile(path.join(__dirname, 'public', '404.html')); });
如果日志显示请求路径不是/webhook,检查Stripe CLI的转发命令是否有误,避免路径末尾带斜杠(如/webhook/)。
3. 确认模块引入路径正确性
检查webhookRoutes.js的引入路径const webhookRoutes = require('./routes/webhookRoutes');,确保项目结构中routes文件夹下存在该文件,注意大小写(Linux/macOS环境下文件名大小写敏感)。
4. 快速验证路由可用性
临时简化/webhook路由,直接返回200测试是否能接收请求:
// 替换index.js中的webhook路由配置 app.post('/webhook', (req, res) => { console.log('Received webhook request'); res.status(200).send('OK'); });
如果能收到日志并返回200,说明路由本身没问题,问题出在后续的Stripe签名验证或控制器逻辑;如果还是404,重点排查端口、路径匹配或文件引入问题。
内容的提问来源于stack exchange,提问作者Ray Wu

