Stripe webhooks in Next.js App Router fail for three reasons that don't apply to Pages Router or Express. This page covers all three.
export const config = { api: { bodyParser: false } } is deprecated and silently ignored in App Router. It does nothing. Use request.text() instead.// app/api/webhooks/stripe/route.ts
import Stripe from 'stripe';
import { NextResponse } from 'next/server';
const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!);
const secret = process.env.STRIPE_WEBHOOK_SECRET!;
// FIX 1: request.text() — bodyParser: false is deprecated in App Router
export async function POST(request: Request) {
const body = await request.text();
const sig = request.headers.get('stripe-signature');
if (!sig) {
return NextResponse.json({ error: 'Missing stripe-signature' }, { status: 400 });
}
let event: Stripe.Event;
try {
// FIX 2: Pass raw string — never request.json()
event = stripe.webhooks.constructEvent(body, sig, secret);
} catch (err: any) {
return NextResponse.json({ error: \`Webhook Error: \${err.message}\` }, { status: 400 });
}
switch (event.type) {
case 'checkout.session.completed':
// fulfill order
break;
case 'customer.subscription.deleted':
// cancel subscription
break;
}
return NextResponse.json({ received: true });
}
If you use Clerk, NextAuth, or Better Auth, add your webhook path to the exclusion list. Without this, the middleware returns 401 before your handler runs — silently, with no log entry.
// middleware.ts
import { clerkMiddleware } from '@clerk/nextjs/server';
export default clerkMiddleware();
export const config = {
matcher: [
// Skip webhook routes, static files, internals
'/((?!api/webhooks|_next/static|_next/image|favicon.ico).*)',
'/(api|trpc)(.*)',
],
};
Clerk and NextAuth middleware may not be configured locally. In production, it intercepts all routes including /api/webhooks/stripe. Add the exclusion above and redeploy.
Your local .env uses the test secret, your Vercel environment uses the live secret (or vice versa). They look identical — both start with whsec_ — but are different values.
If your route runs on Vercel Edge or Cloudflare Workers, Buffer is not available. Use the Web Crypto API instead — or use WebhookFix with the Edge toggle enabled to generate the correct handler.
Generate this handler automatically — with Edge Runtime and auth middleware toggles.
Open WebhookFix →