Fix Stripe Webhook in Next.js App Router

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.

App Router breaking change: export const config = { api: { bodyParser: false } } is deprecated and silently ignored in App Router. It does nothing. Use request.text() instead.

The complete handler

// 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 });
}

FIX 3 — Exclude from auth middleware

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)(.*)',
  ],
};

Works locally, fails in production?

Most common cause
Auth middleware only runs in production

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.

Second most common cause
Test vs live signing secret

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.

Edge Runtime variant

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 →
For informational purposes only. Always test in staging before production. MetricLogic accepts no responsibility for issues arising from use of these tools. © 2026 MetricLogic.
HttpFixer by MetricLogic · Blog · All Tools · Generators MIT · GitHub →