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

在NextJS中如何同时验证LemonSqueezy Webhook签名并使用请求体?

解决NextJS中LemonSqueezy Webhook签名验证与请求体复用问题

问题根源在于请求体是可读流,一旦被读取(比如验证签名时调用await req.text()),流就会被消耗,后续无法再通过req.json()读取。克隆请求无效是因为NextJS的请求对象在读取后,克隆也无法恢复已消耗的流。

直接通过以下步骤解决:

  1. 一次性读取原始请求体文本并保存,同时用于签名验证和数据解析
  2. 避免多次读取请求流

Pages Router 完整代码示例

import type { NextApiRequest, NextApiResponse } from 'next';
import crypto from 'crypto';

const WEBHOOK_SECRET = process.env.LEMON_SQUEEZY_WEBHOOK_SECRET!;

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

  // 1. 读取原始请求体文本,仅读取一次
  const rawBody = await new Promise<string>((resolve, reject) => {
    let body = '';
    req.on('data', chunk => body += chunk.toString());
    req.on('end', () => resolve(body));
    req.on('error', reject);
  });

  // 2. 验证签名
  const signature = req.headers['x-lemon-squeezy-signature'] as string;
  const hmac = crypto.createHmac('sha256', WEBHOOK_SECRET);
  const digest = Buffer.from(hmac.update(rawBody).digest('hex'), 'utf8');
  const signatureBuffer = Buffer.from(signature, 'utf8');

  if (!crypto.timingSafeEqual(digest, signatureBuffer)) {
    return res.status(401).json({ message: 'Invalid signature' });
  }

  // 3. 解析请求体为JSON并使用
  try {
    const payload = JSON.parse(rawBody);
    // 这里编写业务逻辑,比如更新数据库、发送通知等
    console.log('Webhook payload:', payload);
    return res.status(200).json({ message: 'Success' });
  } catch (err) {
    return res.status(400).json({ message: 'Invalid JSON payload' });
  }
}

// 关键配置:禁用NextJS默认请求体解析,避免提前消耗流
export const config = {
  api: {
    bodyParser: false,
  },
};

App Router(NextJS 13+)代码示例

import { NextRequest, NextResponse } from 'next/server';
import crypto from 'crypto';

const WEBHOOK_SECRET = process.env.LEMON_SQUEEZY_WEBHOOK_SECRET!;

export async function POST(req: NextRequest) {
  // 读取原始请求体文本
  const rawBody = await req.text();

  // 验证签名
  const signature = req.headers.get('x-lemon-squeezy-signature')!;
  const hmac = crypto.createHmac('sha256', WEBHOOK_SECRET);
  const digest = Buffer.from(hmac.update(rawBody).digest('hex'), 'utf8');
  const signatureBuffer = Buffer.from(signature, 'utf8');

  if (!crypto.timingSafeEqual(digest, signatureBuffer)) {
    return NextResponse.json({ message: 'Invalid signature' }, { status: 401 });
  }

  // 解析并使用payload
  const payload = JSON.parse(rawBody);
  // 编写业务逻辑
  console.log('Webhook payload:', payload);

  return NextResponse.json({ message: 'Success' });
}

关键说明

  • 禁用默认bodyParser(Pages Router):NextJS默认会自动解析请求体,导致无法获取原始未解析文本用于签名验证,必须手动禁用。
  • 复用原始文本:通过一次读取获取完整原始请求体,既用于签名验证,又解析成JSON使用,避免流被多次消耗。
  • timingSafeEqual安全验证:避免时序攻击,是签名验证的安全最佳实践。

内容的提问来源于stack exchange,提问作者Rik Nieu

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.05 10:50:47