Next.js App Router provides modern Route Handlers (app/api/webhooks/[provider]/route.ts) based on the Web Standard Request and Response APIs.
The most common mistake developers make when building webhook receivers in Next.js is calling await req.json() before signature verification. Because cryptographic HMAC algorithms require the exact, unmutated bytes of the original request, parsing JSON first alters whitespace and character encoding, resulting in signature verification failure.
The Standard Pattern: req.text()
In Next.js Route Handlers, use await req.text() to get the raw body string:
// app/api/webhooks/stripe/route.ts
import { NextResponse } from 'next/server';
import Stripe from 'stripe';
const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!);
export async function POST(req: Request) {
const body = await req.text(); // Exact raw string for signature
const signature = req.headers.get('stripe-signature');
if (!signature) {
return NextResponse.json({ error: 'Missing signature' }, { status: 400 });
}
let event: Stripe.Event;
try {
event = stripe.webhooks.constructEvent(
body,
signature,
process.env.STRIPE_WEBHOOK_SECRET!
);
} catch (err: any) {
return NextResponse.json({ error: `Webhook Error: ${err.message}` }, { status: 400 });
}
// Handle the event
switch (event.type) {
case 'checkout.session.completed':
// Trigger async processing
break;
}
return NextResponse.json({ received: true }, { status: 200 });
} Runtime Considerations: Node.js vs Edge
By default, Route Handlers run on the Node.js runtime (export const runtime = 'nodejs'), which supports full Node cryptographic APIs (node:crypto) and standard SDKs.
If running on Edge Runtime (export const runtime = 'edge'), ensure your verification library uses the Web Crypto API (crypto.subtle) rather than native Node modules.
Avoiding Serverless Function Timeouts
Vercel and serverless platforms terminate serverless executions after 10-15 seconds (on Hobby/Pro plans). If your webhook handler runs database migrations or external API calls, the execution may time out, prompting the webhook provider to retry.
- Return
200 OKas soon as the signature is verified and the event ID is recorded. - Offload long-running tasks to background queues (Inngest, QStash, BullMQ) or Vercel
waitUntil().