บทความทั้งหมด
Next.jsApp Routerwebhook debugginglocal testing

Next.js webhook บน localhost ทำให้ถูก

แหล่งใหญ่ที่สุดของ handler webhook ที่พังใน Next.js App Router คือ body ดิบ App Router ไม่มีปัญหา route handler ไม่มีปัญหา แต่ข้อตกลงในการดึง body ที่ยังไม่ parse เปลี่ยนระหว่าง Pages Router และ App Router และการเชื่อมต่อ Stripe / GitHub / Shopify ส่วนใหญ่คัดลอก snippet จากโลกเก่า บทความนี้คือโลกใหม่

กฎสองบรรทัด

ใน route handler ของ App Router ดึง body ดิบด้วย await request.text() อย่าใช้ request.json() ก่อนการตรวจลายเซ็น อย่าพยายามปิด bodyParser แบบที่ทำใน Pages Router — แฟล็กนั้นไม่มีแล้ว

// app/api/webhooks/stripe/route.ts
import Stripe from 'stripe';
import { headers } from 'next/headers';
import { NextResponse } from 'next/server';

const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!);

export async function POST(request: Request) {
  const body = await request.text();   // raw, not parsed
  const signature = (await headers()).get('stripe-signature')!;

  let event;
  try {
    event = stripe.webhooks.constructEvent(
      body,
      signature,
      process.env.STRIPE_WEBHOOK_SECRET!,
    );
  } catch (err) {
    return NextResponse.json(
      { error: 'invalid signature' },
      { status: 400 },
    );
  }

  // Handle the event
  switch (event.type) {
    case 'checkout.session.completed':
      // ...
      break;
  }

  return NextResponse.json({ received: true });
}

สามบรรทัดควรสนใจ: request.text() ให้สตริงดิบที่ Stripe เซ็น headers() ใน App Router เป็น async — สังเกต await และการตรวจลายเซ็นต้องเกิดก่อนอะไรก็ตามที่เทียบเท่า JSON.parse

นิสัย Pages Router ที่ต้องเลิก

ถ้าคุณย้ายมาจาก Pages Router คุณจำการเขียนนี้ได้:

// pages/api/webhooks/stripe.ts (OLD)
export const config = { api: { bodyParser: false } };

config นั้นไม่มีใน App Router body ถูกอ่านตามต้องการจากออบเจ็กต์ Request สิ่งที่คุณอ่านเป็นอันที่สองจะว่าง — request.text() หลัง request.json() คืนค่าว่าง เลือกหนึ่ง และสำหรับ webhook เลือก text() เสมอ

ตั้งค่า tunnel สำหรับ dev Next.js

  1. รัน next dev (หรือ npm run dev) — มักพอร์ต 3000
  2. ในอีกเทอร์มินัล npx portpreview 3000
  3. วาง URL HTTPS ในการตั้งค่า webhook ของผู้ให้บริการ สำหรับ Stripe: Developers → Webhooks → Add endpoint
  4. ทริกเกอร์เหตุการณ์ทดสอบ คำขอถึง route handler ของคุณพร้อม header ครบถ้วน

ถ้าใช้ next dev --turbo ยังใช้ได้ Turbopack เปลี่ยน bundler ไม่ใช่โมเดล request/response

Edge runtime: อย่า

ถ้าตั้ง export const runtime = 'edge' บน route handler webhook คุณจะสูญเสียการเข้าถึง API crypto เฉพาะ Node ที่ SDK ผู้ให้บริการบางตัวใช้ Stripe Node SDK ทำงานบน edge ในเวอร์ชันล่าสุด แต่การตรวจลายเซ็นกับผู้ให้บริการใด ๆ ไม่แน่นอน เก็บ route webhook ไว้บน Node runtime เว้นแต่มีเหตุผลเฉพาะ

export const runtime = 'nodejs'; // explicit, safe default for webhooks

กับดัก App Router ที่เราเจอบ่อย

คืนชนิดผิด

ถ้าคืน new Response(JSON.stringify(...)) แทน NextResponse.json(...) header content-type อาจไม่ถูกตั้ง ผู้ให้บริการบางรายสนใจ ใช้ NextResponse.json() หรือตั้ง header เอง

streaming และ websocket

handler webhook เป็นคำขอ HTTP อายุสั้น แต่ถ้าคุณ tunnel แอป Next.js ที่มี route WebSocket ด้วย (เช่น socket.io บนเซิร์ฟเวอร์กำหนดเอง) ตรวจให้แน่ใจว่า tunnel รักษา upgrade WebSocket ส่วนใหญ่ทำ; ดูเอกสาร

middleware ที่อ่าน body

ถ้ามี middleware.ts ที่ราก ที่เรียก request.text() เพื่อ log หรือ auth route webhook ของคุณไม่เห็น body เลย ข้ามเส้นทาง webhook ใน matcher หรืออ่าน body เฉพาะใน route ที่ต้องการ

การ deploy บน Vercel ไม่ช่วยคุณ

บั๊ก webhook ที่ขึ้นกับการ parse body ดิบทำงานเหมือนกันบน Vercel และ localhost เมื่อการทดสอบ tunnel ในเครื่องผ่านการตรวจลายเซ็น เวอร์ชันที่ deploy ก็ผ่านด้วย — เว้นแต่คุณชน limit หน่วยความจำหรือ timeout ฝั่ง serverless ซึ่งอาการต่างกัน (5xx ไม่ใช่ 401)

รายละเอียด Stripe ดู คู่มือทดสอบ webhook Stripe ในเครื่อง คณิตของลายเซ็นโดยทั่วไป คู่มือตรวจลายเซ็น เข้าร่วมรายชื่อรอของ PortPreview เพื่อรับ tunnel ที่เป็นมิตรกับ Next.js พร้อม capture

คำถามที่พบบ่อย

ดึง body ดิบใน webhook Next.js App Router อย่างไร?
เรียก await request.text() ภายใน handler POST อย่าเรียก request.json() ก่อน — เมื่อ body ถูกอ่านแล้ว การเรียก .text() หรือ .json() ครั้งที่สองคืนค่าว่าง
ต้องใช้ bodyParser: false ใน Next.js App Router ไหม?
ไม่ config นั้นเป็นรูปแบบ Pages Router และไม่มีสิ่งเทียบเท่าใน App Router body ถูกอ่านตามต้องการจากออบเจ็กต์ Request และคุณอ่านเป็นข้อความดิบเมื่อต้องตรวจลายเซ็น
รัน route webhook บน edge runtime ของ Next.js ได้ไหม?
ได้ แต่ SDK ตรวจลายเซ็นบางครั้งพึ่ง API crypto ของ Node ที่ไม่มีบน edge ใช้ Node runtime ค่าเริ่มต้นสำหรับ webhook เว้นแต่มีเหตุผลเฉพาะที่จะเปลี่ยน