# Stripe webhooks on Next.js Pages Router

Pages Router API routes parse the JSON body before your handler runs. Stripe's signature is computed over the raw bytes, so a parsed-then-reserialized body fails verification. Stripe's own troubleshooting docs call this out: disable bodyParser and read the request as a buffer.

## The setup

```
import Stripe from 'stripe';
import { buffer } from 'micro';

const stripe = new Stripe(process.env.STRIPE_API_KEY_VALUE);

export const config = {
  api: { bodyParser: false },
};

export default async function handler(req, res) {
  if (req.method !== 'POST') {
    res.setHeader('Allow', 'POST');
    return res.status(405).end('method not allowed');
  }
  const rawBody = await buffer(req);
  const sig = req.headers['stripe-signature'];
  let event;
  try {
    event = stripe.webhooks.constructEvent(
      rawBody.toString('utf8'),
      sig,
      process.env.STRIPE_WEBHOOK_SIGNING_VALUE
    );
  } catch (err) {
    return res.status(400).send('bad signature');
  }
  // switch on event.type ...
  res.status(200).json({ received: true });
}
```

## Why this differs from App Router

App Router route handlers get a standard Request and you call `request.text()`. Pages Router gives you a Node req that Next.js has already consumed, so you must opt out with `bodyParser: false` and buffer the stream yourself. Mixing the two patterns is the common failure when an agent copies an App Router example into a Pages Router project.

## Checklist

- `bodyParser: false` is set, otherwise the stream is already consumed.
- Pass the buffer as a UTF-8 string to constructEvent, not a parsed object.
- The signing value comes from the webhook endpoint settings in the Dashboard.
- Return 400 on signature failure, 200 only after accepting the event.