Stripe Webhooks

Learn how to handle Stripe events and keep your application in sync with Stripe.

Webhook Setup

Create a webhook endpoint to receive Stripe events:

// app/api/webhooks/stripe/route.ts
import { headers } from "next/headers";
import { stripe } from "@/lib/stripe/client";
export async function POST(request: Request) {
const body = await request.text();
const signature = headers().get("stripe-signature")!;
try {
const event = stripe.webhooks.constructEvent(
body,
signature,
process.env.STRIPE_WEBHOOK_SECRET!
);
// Handle the event
switch (event.type) {
case "customer.subscription.created":
await handleSubscriptionCreated(event.data.object);
break;
case "customer.subscription.updated":
await handleSubscriptionUpdated(event.data.object);
break;
case "customer.subscription.deleted":
await handleSubscriptionDeleted(event.data.object);
break;
// Add more event handlers as needed
}
return new Response(null, { status: 200 });
} catch (error) {
console.error("Error handling webhook:", error);
return new Response(
JSON.stringify({ error: "Webhook handler failed" }),
{ status: 400 }
);
}
}

Event Handlers

Implement handlers for different Stripe events:

// Handle subscription created
async function handleSubscriptionCreated(subscription: Stripe.Subscription) {
await db.subscription.create({
data: {
id: subscription.id,
userId: subscription.metadata.userId,
status: subscription.status,
priceId: subscription.items.data[0].price.id,
currentPeriodEnd: new Date(subscription.current_period_end * 1000),
cancelAtPeriodEnd: subscription.cancel_at_period_end,
},
});
}
// Handle subscription updated
async function handleSubscriptionUpdated(subscription: Stripe.Subscription) {
await db.subscription.update({
where: { id: subscription.id },
data: {
status: subscription.status,
priceId: subscription.items.data[0].price.id,
currentPeriodEnd: new Date(subscription.current_period_end * 1000),
cancelAtPeriodEnd: subscription.cancel_at_period_end,
},
});
}
// Handle subscription deleted
async function handleSubscriptionDeleted(subscription: Stripe.Subscription) {
await db.subscription.update({
where: { id: subscription.id },
data: {
status: subscription.status,
currentPeriodEnd: new Date(subscription.current_period_end * 1000),
},
});
}

Testing Webhooks

Use the Stripe CLI to test webhooks locally:

# Install Stripe CLI
brew install stripe/stripe-cli/stripe
# Login to your Stripe account
stripe login
# Forward webhooks to your local server
stripe listen --forward-to localhost:3000/api/webhooks/stripe
# Trigger test events
stripe trigger payment_intent.succeeded
stripe trigger customer.subscription.created
stripe trigger customer.subscription.updated

Important Events

Subscription Events

  • customer.subscription.created
  • customer.subscription.updated
  • customer.subscription.deleted
  • customer.subscription.trial_will_end

Payment Events

  • payment_intent.succeeded
  • payment_intent.payment_failed
  • invoice.paid
  • invoice.payment_failed

Best Practices

  • Always verify webhook signatures
  • Handle events idempotently
  • Implement proper error handling
  • Log webhook events for debugging
  • Set up webhook monitoring

Next Steps