Stripe webhook · server log

Missing raw body / body already used

what it looks like
TypeError: Body is unusable: Body has already been read  ·  Webhook payload must be provided as a string or a Buffer

What it means

The request body stream can be read once. If something read it already, or you pass an object instead of the raw string, verification can't run. The Pages Router bodyParser: false config does nothing in App Router route handlers.

Common causes

  • Reading req.json() for logging, then req.text() for verification
  • Copying a Pages Router example (export const config = { api: { bodyParser: false } }, micro's buffer())
  • Passing a parsed object to constructEvent

How to fix it

  1. Read once: const body = await req.text()
  2. Delete Pages Router body config and the micro dependency from App Router routes
  3. If you need to log, log after verification using the event object
src/app/api/stripe/webhook/route.ts
// src/app/api/stripe/webhook/route.ts
import Stripe from "stripe";

export const runtime = "nodejs";

const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!);

export async function POST(req: Request) {
  const sig = req.headers.get("stripe-signature");
  if (!sig) return new Response("Missing signature", { status: 400 });
  const body = await req.text(); // raw body: never req.json()
  let event: Stripe.Event;
  try {
    event = stripe.webhooks.constructEvent(body, sig, process.env.STRIPE_WEBHOOK_SECRET!);
  } catch (err) {
    return new Response(`Invalid signature: ${(err as Error).message}`, { status: 400 });
  }
  // ...handle event.type...
  return Response.json({ received: true });
}
verify the fix
stripe listen --forward-to localhost:3000/api/stripe/webhook
# put the whsec_... it prints in .env.local, then restart npm run dev
stripe trigger checkout.session.completed

pass if stripe listen prints [200] for every forwarded event, not 400 or 500.

works with stripe-node 23.0.0 · Next.js 16.4.3 App Routerlast verified CLI steps: Stripe CLI 1.55.2, Oct 27, 2026

prevent it · stripe-webhooks.mdc (free Cursor Rules Pack): Never calls req.json() before verifying the signature.

Prompt for Cursor or Claude Code

My Stripe webhook can't get the raw body. Rewrite src/app/api/stripe/webhook/route.ts for the App Router: read the body once with await req.text(), verify with constructEvent, remove any Pages Router bodyParser config or micro buffer() code, and don't read the body anywhere else.

Not quite your error? Paste yours into the free VibeBoot Doctor or browse the Stripe webhook fixer.

next: skip the next one

Skip the debugging. The VibeBoot SaaS Starter ships with this already handled: a signature-verified webhook (raw body + constructEvent) that re-fetches the subscription from Stripe before saving it, with Vitest tests. Plus auth and a database on Next.js 16.4.3. $49, one-time.

14-day money-back guarantee · lifetime updates

$ fixed it? get the next one first

New fixes for what AI agents break, by email.

When we add a fix or a Cursor rule for a new Next.js, Auth.js, Prisma or Stripe break, it goes out in the VibeBoot newsletter. Free, occasional, unsubscribe in one click.

Subscribe free