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

解决Next.js中Stripe Webhook引发的MaxListenersExceededWarning问题

问题诊断与解决方案

核心原因

Next.js Pages API属于无服务器函数架构,每次请求(包括Stripe Webhook回调)会触发模块的重新加载(开发环境热重载或生产环境冷启动),如果在请求处理函数内重复初始化Stripe实例、绑定Webhook监听器,或者Prisma客户端未采用单例模式,会导致EventEmitter监听器累积超过默认上限(10个),进而触发内存泄漏警告,同时因监听器冲突或资源占用导致Prisma数据库更新失败。而Express是长期运行的服务器,模块仅初始化一次,因此无此问题。

解决方案

1. 确保Stripe实例与Webhook逻辑单例化

将Stripe实例、Webhook密钥等初始化逻辑放在模块级别,利用Next.js的模块缓存机制避免重复创建:

// pages/api/stripe/webhook.js
import Stripe from 'stripe';
import prisma from '../../../lib/prisma';

// 模块级别初始化,仅执行一次
const stripe = new Stripe(process.env.STRIPE_SECRET_KEY, {
  apiVersion: '2024-06-20', // 替换为你的Stripe API版本
});
const webhookSecret = process.env.STRIPE_WEBHOOK_SECRET;

export default async function handler(req, res) {
  if (req.method !== 'POST') return res.status(405).end();

  let event;
  try {
    // 使用预初始化的Stripe实例处理事件
    event = stripe.webhooks.constructEvent(
      req.rawBody,
      req.headers['stripe-signature'],
      webhookSecret
    );
  } catch (err) {
    return res.status(400).send(`Webhook Error: ${err.message}`);
  }

  // 处理订阅事件示例
  switch (event.type) {
    case 'customer.subscription.created':
    case 'customer.subscription.updated': {
      const subscription = event.data.object;
      try {
        await prisma.subscription.update({
          where: { stripeSubscriptionId: subscription.id },
          data: {
            status: subscription.status,
            currentPeriodEnd: new Date(subscription.current_period_end * 1000),
            // 补充你的其他更新字段
          },
        });
      } catch (dbErr) {
        console.error('DB更新失败:', dbErr);
        // 返回500让Stripe自动重试
        return res.status(500).end();
      }
      break;
    }
    default:
      console.log(`未处理事件类型: ${event.type}`);
  }

  res.json({ received: true });
}

// 禁用Next.js默认body解析,确保获取原始请求体
export const config = {
  api: {
    bodyParser: false,
  },
};

2. 确保Prisma客户端单例化

避免在请求处理函数内重复创建Prisma实例,创建全局单例:

// lib/prisma.js
import { PrismaClient } from '@prisma/client';

let prisma;

if (process.env.NODE_ENV === 'production') {
  prisma = new PrismaClient();
} else {
  // 开发环境用global变量缓存实例,避免热重载重复创建
  if (!global.prisma) {
    global.prisma = new PrismaClient();
  }
  prisma = global.prisma;
}

export default prisma;

3. 临时缓解:调整EventEmitter监听器上限(不推荐作为根本解决方案)

如果上述方案未立即生效,可临时提高监听器上限,但需排查根本原因:

// 在webhook.js顶部添加
require('events').EventEmitter.defaultMaxListeners = 20;

关键注意事项

  • 必须禁用Next.js默认的body解析,否则无法获取Stripe需要的原始请求体
  • 数据库更新失败时返回500状态码,触发Stripe自动重试机制,避免数据不一致
  • 开发环境下Next.js热重载会导致模块重新加载,需用global变量缓存单例实例

内容的提问来源于stack exchange,提问作者JEAN PABLO CALDERON RAMIREZ

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.15 14:56:36