How to Add Stripe Webhook using NextJS 13 App Router

Jasser Mark Arioste

Jasser Mark Arioste

How to Add Stripe Webhook using  NextJS 13 App Router

Hello! In this tutorial, you'll learn how to add a stripe webhook using NextJS 13 /app router.

Introduction #

The NextJS team introduced version 13.4 fairly recently and this version marks the /app router as stable meaning it's pretty OK to use for production. I was wondering how to do things in the new /app router as opposed to the /pages router, specifically stripe webhooks. Would it be easier or harder to implement? The answer? definitely easier.

Prerequisites #

I assume you have basic knowledge of Next.js and a bit of knowledge from Stripe. At a minimum, you need a stripe API key to create the webhook endpoint and also to log in to stripe CLI for local development testing.

I assume you already have a basic knowledge of what a webhook is.

Tutorial Objectives #

By the end of this tutorial, you should be able to learn the following:

  1. Create a stripe webhook using the NextJS app router
  2. Install and login to stripe CLI locally
  3. Test the stripe webhook using the stripe CLI

Step 0: Project Setup (Optional) #

In this step, we're going to create a new NextJS project by using the create-next-app CLI. But if you already have a NextJS project that uses the /app router, you can skip this step and proceed to step 2.

To create a new NextJS project that uses the /app router. Simply execute the following command in your command line:

npx create-next-app@latest --ts --app next-js-stripe-webhook-tutorial

The --ts option will initialize the project with Typescript and the --app option will initialize the project using the /app directory instead of the /pages directory.

After this, you can start your local development server by running the following command:

cd next-js-stripe-webhook-tutorial
yarn dev 
# or 
npm run dev

Step 1: Installing Dependencies #

In this step, we'll install the stripe package to access its API. The stripe package already has Typescript support built in so we don't have to install any extra dependencies for it. Simply invoke the following command:

yarn add stripe
#or npm
npm install stripe

Step 2: Adding Environment Variables #

In this step, we'll add the stripe API keys to an .env.local file. I assume you have test API keys from the stripe dashboard which are denoted by sk_test_ and pk_test_ prefixes. 

First, create the .env.local file at the root directory of your project and add your keys:


Step 3: Creating the stripe webhook route #

Next, let's create a stripe webhook API route. So whenever a stripe event occurs, stripe sends a POST request to this route so that we'll be able to handle anything and maybe change the data in our database.

First, create the file app/api/stripe-webhook/route.ts. The route.ts file is a special file convention to mark the current route as an API route rather than a page. For pages we use page.ts.

// app/api/stripe-webhook/route.ts

import { NextResponse } from "next/server";

export async function POST(req: Request) {
  return new NextResponse("", { status: 200, statusText: "success" });

Next is to parse the Request object to get the event from Stripe.  Modify your code to use the code below:

// app/api/stripe-webhook/route.ts
import { NextResponse } from "next/server";
import { Stripe } from "stripe";

const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!, {
  apiVersion: "2022-11-15",

// Stripe will give you a webhook secret when setting up webhooks.
// well get this later and add it to the .env.local file when testing
const webhookSecret = process.env.STRIPE_WEBHOOK_SECRET!;

export async function POST(req: Request) {
  const payload = await req.text();
  const signature = req.headers.get("stripe-signature");

  let event: Stripe.Event | null = null;
  try {
    event = stripe.webhooks.constructEvent(payload, signature!, webhookSecret);
    switch (event?.type) {
      case "payment_intent.succeeded":
        // handle payment_intent.succeded
      case "xxx":
        // handle other type of stripe events
        // other events that we don't handle
  } catch (err) {
    if (err instanceof Error) {
      return NextResponse.json({ message: err.message }, { status: 400 });
  return NextResponse.json({ received: true });


To parse the specific event coming from Stripe, we have to use the stripe.webhooks.constructEvent() method. The constructEvent method accepts 3 parameters: request payload, signature, and the webhook secret.

You get the request payload by simply using req.text() method on line 14.

You get the signature by using req.headers.get("stripe-signature") on line 15.

You get the webhook secret using an environment variable, we'll set this up later in the next step.

On lines 20-30, we handle specific event types by using a switch-case block. 

Step 4: Installing Stripe CLI #

To ensure that our webhook works, we'll have to test it, at least in our local development environment. In this step, we'll install the stripe-cli to be able to log in and set up a webhook listener. First, install stripe-cli globally. How to install it is different depending on your OS so I recommend following the steps in the documentation. How to Install Stripe CLI

Once you've successfully installed it, you should be able to run stripe -v and get the version for your cli.

stripe -v
# stripe version 1.7.9

Step 5: Logging into the Stripe CLI #

In this step, we'll log in to the stripe CLi using our test API key. To do this, simply run the following command (make sure you modify sk_test_xxxx to your own API key):

stripe login --api-key sk_test_xxxxxx

Once successful, it should output something like the following:

# Done! The Stripe CLI is configured for Test Account with account id acct_xxxxxxxxxxxxx

Step 6: Creating a Local Stripe Event Listener #

In this step, we'll create a local event listener so that events from Stripe will be forwarded to our local development environment. To do this, just invoke the following command:

stripe listen --forward-to localhost:3000/api/stripe-webhook

Note that localhost:3000/api/stripe-webhook is the route that we created earlier in step 3.

Once successful, it should give you the webhook secret key as shown below:

#Ready! You are using Stripe API Version [2020-08-27]. 
#Your webhook signing secret is whsec_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

Next is to add the webhook secret key in your .env.local file:

# env.local

Step 7: Triggering Stripe Events Locally #

The last step is to check if there's no problem with parsing the stripe events with our webhook by triggering events. To do this locally, we can jut use the stripe-cli. For example:

stripe trigger payment_intent.succeeded

If everything is according to plan, your stripe webhook event listener should output something like the following:

# 2023-07-15 10:53:57   --> charge.succeeded [evt_3NTycHAIj92HmZ6b1HfAr9tb]
# 2023-07-15 10:53:57   --> payment_intent.succeeded [evt_3NTycHAIj92HmZ6b1WvwI5vU]
# 2023-07-15 10:53:57   --> payment_intent.created [evt_3NTycHAIj92HmZ6b1V1xYfT5]
# 2023-07-15 10:53:59  <--  [200] POST http://localhost:3000/api/stripe-webhook [evt_3NTycHAIj92HmZ6b1HfAr9tb]
# 2023-07-15 10:53:59  <--  [200] POST http://localhost:3000/api/stripe-webhook [evt_3NTycHAIj92HmZ6b1V1xYfT5]
# 2023-07-15 10:53:59  <--  [200] POST http://localhost:3000/api/stripe-webhook [evt_3NTycHAIj92HmZ6b1WvwI5vU]
# 2023-07-15 11:15:18   --> charge.succeeded [evt_3NTywwAIj92HmZ6b3VFKmRva]
# 2023-07-15 11:15:18   --> payment_intent.succeeded [evt_3NTywwAIj92HmZ6b3IrVuCBS]
# 2023-07-15 11:15:18   --> payment_intent.created [evt_3NTywwAIj92HmZ6b3DNszQpo]
# 2023-07-15 11:15:18  <--  [200] POST http://localhost:3000/api/stripe-webhook [evt_3NTywwAIj92HmZ6b3IrVuCBS]
# 2023-07-15 11:15:18  <--  [200] POST http://localhost:3000/api/stripe-webhook [evt_3NTywwAIj92HmZ6b3VFKmRva]
# 2023-07-15 11:15:18  <--  [200] POST http://localhost:3000/api/stripe-webhook [evt_3NTywwAIj92HmZ6b3DNszQpo]

If your webhook secret is incorrect, it will give you the following error:

No signatures found matching the expected signature for payload. Are you passing the raw request body you received from Stripe?
Learn more about webhook signing and explore webhook integration examples for various frameworks at

That's basically it! 

Conclusion #

You've successfully implemented a stripe webhook using NextJS 13 /app router. You've also confirmed that it is working by testing it locally using the stripe-cli.

If you like this tutorial, please leave a like or share this article. For future tutorials like this, please subscribe to our newsletter or follow me on GitHub.

Credits: Image by t 林 from Pixabay

Share this post!

Related Posts


This content may contain links to products, software and services. Please assume all such links are affiliate links which may result in my earning commissions and fees.
As an Amazon Associate, I earn from qualifying purchases. This means that whenever you buy a product on Amazon from a link on our site, we receive a small percentage of its price at no extra cost to you. This helps us continue to provide valuable content and reviews to you. Thank you for your support!
Donate to ReactHustle