Welcome back to our journey into Stripe for Next.js! In the previous sections, we explored how Stripe sends notifications about events happening in your account through webhooks. Now, let's tackle a crucial aspect of handling these webhooks: security. When Stripe sends a webhook to your Next.js application, it's vital to verify that the request genuinely originated from Stripe and hasn't been tampered with. This is where webhook signature verification comes in.
Stripe signs every webhook request with a digital signature. This signature is generated using your webhook signing secret, which you can find in your Stripe dashboard under Developers -> Webhooks. By verifying this signature on your end, you can ensure the integrity and authenticity of the incoming webhook payload. This prevents malicious actors from sending fake events to your application and potentially causing unexpected behavior or data breaches.
Stripe provides a robust library to help you with this verification process. We'll be using the official Stripe Node.js library, which includes a dedicated function for verifying webhook signatures. Let's see how to implement this in your Next.js API route.
import type { NextApiRequest, NextApiResponse } from 'next';
import Stripe from 'stripe';
const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!, {
apiVersion: '2022-11-15',
});
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
if (req.method !== 'POST') {
return res.setHeader('Allow', 'POST').status(405).end('Method Not Allowed');
}
const signature = req.headers['stripe-signature'] as string;
const webhookSecret = process.env.STRIPE_WEBHOOK_SECRET;
if (!webhookSecret) {
console.error('STRIPE_WEBHOOK_SECRET is not set!');
return res.status(500).send('Webhook secret not configured.');
}
try {
const event = stripe.webhooks.constructEvent(
req.body,
signature,
webhookSecret
);
// Handle the event
switch (event.type) {
case 'payment_intent.succeeded':
const paymentIntent = event.data.object;
console.log(`PaymentIntent for ${paymentIntent.amount} was successful!`);
// Fulfill the purchase...
break;
case 'payment_method.attached':
const paymentMethod = event.data.object;
console.log(`PaymentMethod ${paymentMethod.id} was attached.`);
// ... handle other event types
break;
default:
console.log(`Unhandled event type ${event.type}`);
}
res.status(200).json({ received: true });
} catch (err: any) {
console.error(`Webhook signature verification failed: ${err.message}`);
return res.status(400).send(`Webhook Error: ${err.message}`);
}
}In this code snippet, we're doing a few key things:
- Retrieving the Signature: We extract the
stripe-signatureheader from the incoming request. - Accessing the Secret: We fetch your
STRIPE_WEBHOOK_SECRETfrom your environment variables. Crucially, never hardcode your webhook secret directly into your code. Always use environment variables for sensitive information. - Constructing the Event: The
stripe.webhooks.constructEvent()function is the heart of the verification process. It takes the raw request body, the signature, and your webhook secret. If the signature is invalid, this function will throw an error. - Error Handling: We wrap the
constructEventcall in atry...catchblock. If verification fails, we log the error and return a 400 Bad Request response to Stripe, indicating an issue. - Handling Valid Events: If the signature is valid, the
constructEventfunction returns a StripeEventobject. You can then proceed to handle the event based on itstype, just as we discussed in the previous sections.
This simple yet powerful verification mechanism adds a critical layer of security to your Stripe webhook integration. By always verifying signatures, you can trust that the data you're receiving is legitimate and comes directly from Stripe.
graph TD
Stripe[Stripe Server] -->|POST /webhook| NextJSApp[Next.js API Route]
NextJSApp --|Signature Header, Request Body| NextJSApp
NextJSApp --|Webhook Secret (from env)| NextJSApp
NextJSApp --|stripe.webhooks.constructEvent()| Verification[Signature Verification]
Verification --|Valid| NextJSApp
Verification --|Invalid| ErrorResponse[400 Bad Request]
NextJSApp --|Process Event| SuccessResponse[200 OK]