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

Express.js Stripe Webhook路由配置正确却返回404错误求助

Express + 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,
};

复现步骤

  1. 启动服务器:node index.js
  2. 使用Stripe CLI转发事件:stripe listen --forward-to http://localhost:3080/webhook
  3. 触发测试事件: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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.22 17:44:57