How to Add Stripe Webhook using NextJS 13 App Router
Jasser Mark Arioste
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:
- Create a stripe webhook using the NextJS app router
- Install and login to stripe CLI locally
- 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:
STRIPE_SECRET_KEY=sk_test_xxxxxxxxxxxxxxxxxx NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY=pk_test_xxxxxxxxxxxxxxx
1
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" }); }
1234567
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 break; case "xxx": // handle other type of stripe events break; default: // other events that we don't handle break; } } catch (err) { if (err instanceof Error) { console.error(err.message); return NextResponse.json({ message: err.message }, { status: 400 }); } } return NextResponse.json({ received: true }); }
1234567891011121314151617181920212223242526272829303132333435363738
Explanation:
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
STRIPE_WEBHOOK_SECRET=whsec_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
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 https://github.com/stripe/stripe-node#webhook-signing
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