Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add webhook handler for Next JS 14 API router #2258

Closed
wants to merge 2 commits into from

Conversation

AbdullahAhmadAAK
Copy link

@AbdullahAhmadAAK AbdullahAhmadAAK commented Feb 5, 2025

Why?

This is to help developers in NextJS 14 by giving them a working example of a webhook handler. That way, they won't be confused as I was for 2-3 hours by not being able to follow the other pages router example.

Addresses #2100

@AbdullahAhmadAAK AbdullahAhmadAAK requested a review from a team as a code owner February 5, 2025 05:36
@AbdullahAhmadAAK AbdullahAhmadAAK requested review from xavdid-stripe and removed request for a team February 5, 2025 05:36
Copy link

cla-assistant bot commented Feb 5, 2025

CLA assistant check
Thank you for your submission! We really appreciate it. Like many open source projects, we ask that you sign our Contributor License Agreement before we can accept your contribution.
You have signed the CLA already but the status is still pending? Let us recheck it.

Copy link
Contributor

@jsteele-stripe jsteele-stripe left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@AbdullahAhmadAAK Thanks for your contribution! Please let me know if you've any questions about my comments/suggestions. Once resolved we can get this merged.

import { NextRequest, NextResponse } from 'next/server';
import Stripe from 'stripe';
import { headers } from 'next/headers'
import { getErrorMessage, logError } from '@/lib/error-methods';
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we remove this import as it looks like code specific to your application and may confuse others.


// Handle the construction of the event
try {
if (!sig) throw new Error("No signature provided")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Redundant as there'll always be a Stripe-Signature header.

const bodyBuffer = Buffer.from(reqArrayBuffer)
event = stripe.webhooks.constructEvent(bodyBuffer, sig, webhookSecret);
} catch (err) {
logError(err)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Application specific – remove.

Comment on lines +20 to +22
const reqArrayBuffer = await req.arrayBuffer()
const bodyBuffer = Buffer.from(reqArrayBuffer)
event = stripe.webhooks.constructEvent(bodyBuffer, sig, webhookSecret);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can simplify this:

Suggested change
const reqArrayBuffer = await req.arrayBuffer()
const bodyBuffer = Buffer.from(reqArrayBuffer)
event = stripe.webhooks.constructEvent(bodyBuffer, sig, webhookSecret);
const body = await req.text();
event = stripe.webhooks.constructEvent(body, sig, webhookSecret);

event = stripe.webhooks.constructEvent(bodyBuffer, sig, webhookSecret);
} catch (err) {
logError(err)
return NextResponse.json({ error: `Webhook Error: ${getErrorMessage(err)}` }, { status: 400 })
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we remove getErrorMessage usage please.

@@ -0,0 +1,3 @@
export const api = {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This file is redundant I believe.

Comment on lines +29 to +39
switch (event.type) {
case 'product.updated': {

}

case 'product.created': {

}

// other cases as you please
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we add a canonical example for event handling? Doesn't need to be extensive

Suggested change
switch (event.type) {
case 'product.updated': {
}
case 'product.created': {
}
// other cases as you please
}
ry {
switch (event.type) {
case "checkout.session.completed":
data = event.data.object as Stripe.Checkout.Session;
console.log(`💰 CheckoutSession status: ${data.payment_status}`);
break;
case "payment_intent.payment_failed":
data = event.data.object as Stripe.PaymentIntent;
console.log(`❌ Payment failed: ${data.last_payment_error?.message}`);
break;
case "payment_intent.succeeded":
data = event.data.object as Stripe.PaymentIntent;
console.log(`💰 PaymentIntent status: ${data.status}`);
break;
default:
throw new Error(`Unhandled event: ${event.type}`);
}
} catch (error) {
console.log(error);
return NextResponse.json(
{ message: "Webhook handler failed" },
{ status: 500 },
);
}

}

// Return a response to acknowledge receipt of the event
return new Response('Event received', { status: 200 });
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's be consistent and use NextResponse.

Suggested change
return new Response('Event received', { status: 200 });
return NextResponse.json({ message: "Event received" }, { status: 200 });

@@ -0,0 +1,43 @@
import { NextRequest, NextResponse } from 'next/server';
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you rename the directory so it resembles the Next.js project structure (app/api/stripe/webhook/route.ts)?

// Webhook handler
export async function POST(req: NextRequest) {
const webhookSecret: string = process.env.STRIPE_WEBHOOK_SECRET!;
const headersList = headers()
Copy link

@seanzhang-stripe seanzhang-stripe Feb 5, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

headers is an async function ( ref ), and we need to use await here . For example

const headersList = await headers();

Alternatively, we can get the headers directly from req

const headersList = req.headers;

@xavdid-stripe
Copy link
Member

closing in favor of #2259.

Thank you for your interest though!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants