Zdecydowanie największym źródłem zepsutych handlerów webhooków w Next.js App Router jest surowe ciało. App Router jest w porządku. Route handlery są w porządku. Ale konwencje pobierania nieparsowanego ciała żądania zmieniły się między Pages Router a App Router, a większość integracji Stripe / GitHub / Shopify kopiuje fragmenty ze starego świata. Ten artykuł to nowy świat.
Reguła dwóch linii
W route handlerze App Routera pobierz surowe ciało przez await request.text(). Nie używaj request.json() przed weryfikacją podpisu. Nie próbuj wyłączać bodyParser tak jak w Pages Router — ta flaga już nie istnieje.
// 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 });
}
Trzy linie zasługują na uwagę: request.text() daje surowy string, który podpisał Stripe. headers() w App Router jest async — zauważ await. A weryfikacja podpisu musi nastąpić przed jakimkolwiek odpowiednikiem JSON.parse.
Nawyk Pages Router do oduczenia
Jeśli migrowałeś z Pages Router, pamiętasz pisanie tego:
// pages/api/webhooks/stripe.ts (OLD)
export const config = { api: { bodyParser: false } };
Tej konfiguracji nie ma w App Router. Ciało jest czytane na żądanie z obiektu Request. Cokolwiek czytasz jako drugie, jest puste — request.text() po request.json() nie zwraca nic. Wybierz jedno, a dla webhooków zawsze wybierz text().
Konfiguracja tunelu dla dev Next.js
- Uruchom
next dev(lubnpm run dev) — zwykle port 3000. - W innym terminalu
npx portpreview 3000. - Wklej URL HTTPS do konfiguracji webhooka swojego dostawcy. Dla Stripe: Developers → Webhooks → Add endpoint.
- Wyzwól zdarzenie testowe. Żądanie trafia do twojego route handlera z wszystkimi nagłówkami nietkniętymi.
Jeśli używasz next dev --turbo, to nadal działa. Turbopack zmienia bundler, nie model request/response.
Edge runtime: nie
Jeśli ustawiłeś export const runtime = 'edge' na route handlerze webhooka, stracisz dostęp do API crypto tylko-Node, których używają niektóre SDK dostawców. Stripe Node SDK działa na edge w niedawnych wersjach, ale weryfikacja podpisu z dowolnymi dostawcami jest niepewna. Trzymaj trasy webhooków na runtime Node, chyba że masz konkretny powód.
export const runtime = 'nodejs'; // explicit, safe default for webhooks
Pułapki App Routera, na które wciąż wpadamy
Zwrócenie złego typu
Jeśli zwracasz new Response(JSON.stringify(...)) zamiast NextResponse.json(...), nagłówek content-type może nie zostać ustawiony. Niektórzy dostawcy się tym przejmują. Używaj NextResponse.json() lub ustaw nagłówek ręcznie.
Streaming i websockety
Handlery webhooków to krótkotrwałe żądania HTTP, ale jeśli tunelujesz aplikację Next.js, która ma też trasy WebSocket (na przykład z socket.io na własnym serwerze), upewnij się, że twój tunel zachowuje upgrade'y WebSocket. Większość zachowuje; sprawdź dokumentację.
Middleware czytające ciało
Jeśli masz middleware.ts w katalogu głównym, który wywołuje request.text() do logowania lub auth, twoja trasa webhooka nigdy nie widzi ciała. Pomiń ścieżkę webhooka w matcherze lub czytaj ciała tylko w trasach, które ich potrzebują.
Deploye Vercel cię nie uratują
Bugi webhooków zależne od parsowania surowego ciała zachowują się identycznie na Vercel i na localhost. Gdy twój lokalny test w tunelu przejdzie weryfikację podpisu, wdrożona wersja też przejdzie — chyba że trafiasz na limit pamięci lub timeoutu po stronie serverless, w którym to przypadku objaw jest inny (5xx, nie 401).
Po szczegóły Stripe zobacz przewodnik lokalnego testowania webhooków Stripe. Po matematykę podpisu ogólnie — przewodnik weryfikacji podpisu. Dołącz do listy oczekujących PortPreview, by dostać przyjazny dla Next.js tunel z przechwytywaniem.