Hand signing a document

Verify Stripe webhook signature in Next.js API Routes

December 14, 2020

I'm using Stripe to power the payment gateway of a Next.js app and got to the step to verify the webhook signing secret. I had some issues at first because Stripe expects the raw request body whereas Next.js parses the request body before it's passed to the handler. This is normally a good thing as it saves you the effort of parsing, but in this case, it presents a problem.

Thankfully, Vercel – the team behind Next.js – have thought about this already and offer a pretty simple solution.

Step 1: Export a config for the handler

Next.js offers a very simple solution to setting a separate config for each handler. This is great because it means you don't have to change all the other endpoints you've set up. The secret? Just export a config object from the file where the handler is defined.

Each route has a default export, which should be the handler, and Next also looks for a named export: config. So the solution (as can be found in the docs here) is to add the following named export to the file:

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

The config tells Next to not parse the request body for this handler. Great! Now you can pass it to the Stripe SDK to verify the signature. But wait, seems like passing it directly doesn't work. You need to create a buffer from the request object. Vercel has created a utility library with a helper method for that.

Step 2: Use the micro library

Install the micro library from npm.

shell
npm i micro

Then import buffer from the library and use it to create a buffer from the whole request object.

Step 3: Use the Stripe SDK to verify the signature

handler.ts
import { NextApiRequest, NextApiResponse } from 'next';
import { buffer } from 'micro';
import Stripe from 'stripe';

export default function handler(req: NextApiRequest, res: NextApiResponse) {
  // check the request method and ensure you only accept POST requests.
  try {
    const requestBuffer = await buffer(req);
    const signature = req.headers['stripe-signature'] as string;
    const stripe = new Stripe(<YOUR-STRIPE-SECRET>, { apiVersion: null }); // version null sets the most recent API version
    const event =  stripe.webhooks.constructEvent(
      requestBuffer.toString(), // Stringify the request for the Stripe library
      signature,
      <YOUR-STRIPE-WEBHOOK-SIGNING-SECRET> // you get this secret when you register a new webhook endpoint
    );
    // you can now safely work with the request. The event returned is the parsed request body.
    res.send(200);
  } catch (error) {
    res.status(400).send(`Webhook error: ${error.message}`);
  }
}

That's it. Now you can do what you need with the request. Stripe throws an error if the signature doesn't match, so make sure to catch it and send a response.

Notes

The above is a very simplified version of the handler. I like to split out all the Stripe related code into a separate lib so I don't have a whole heap of references to create a new Stripe instance all over the codebase. You do you, of course, and organise it however you like. But for simplicity's sake, I've not imposed my own opinions of architecture on you.

If you found the above useful, say thanks on Twitter: @BeppeKarlsson