แหล่งใหญ่ที่สุดของ 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
- รัน
next dev(หรือnpm run dev) — มักพอร์ต 3000 - ในอีกเทอร์มินัล
npx portpreview 3000 - วาง URL HTTPS ในการตั้งค่า webhook ของผู้ให้บริการ สำหรับ Stripe: Developers → Webhooks → Add endpoint
- ทริกเกอร์เหตุการณ์ทดสอบ คำขอถึง 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