Guides / Payments
Stripe fires a different webhook for every subscription state change. Here's one handler that covers them all:
// app/api/webhooks/stripe/route.ts
import Stripe from "stripe";
import { logit } from "@/lib/logit";
const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!);
export async function POST(req: Request) {
const body = await req.text();
const sig = req.headers.get("stripe-signature")!;
let event: Stripe.Event;
try {
event = stripe.webhooks.constructEvent(body, sig, process.env.STRIPE_WEBHOOK_SECRET!);
} catch {
return new Response("Invalid signature", { status: 400 });
}
switch (event.type) {
case "customer.subscription.created": {
const sub = event.data.object as Stripe.Subscription;
await logit.now("subscriptions", {
event: "New subscription started",
description: `Plan: ${sub.items.data[0]?.price.nickname ?? "unknown"}`,
icon: "🎉",
notify: true,
tags: { status: "active", plan: sub.items.data[0]?.price.nickname ?? "" },
metadata: { subscriptionId: sub.id, customerId: sub.customer as string },
});
break;
}
case "customer.subscription.updated": {
const sub = event.data.object as Stripe.Subscription;
const prev = event.data.previous_attributes as Partial<Stripe.Subscription>;
const wasUpgrade = prev?.items !== undefined;
await logit.now("subscriptions", {
event: wasUpgrade ? "Subscription changed" : "Subscription updated",
description: `Status: ${sub.status}`,
icon: "🔄",
notify: false,
metadata: { subscriptionId: sub.id, status: sub.status },
});
break;
}
case "customer.subscription.deleted": {
const sub = event.data.object as Stripe.Subscription;
await logit.now("churn", {
event: "Subscription cancelled",
description: `Customer ${sub.customer} churned`,
icon: "😢",
notify: true,
tags: { reason: sub.cancellation_details?.reason ?? "unknown" },
metadata: { subscriptionId: sub.id, customerId: sub.customer as string },
});
break;
}
case "customer.subscription.paused": {
const sub = event.data.object as Stripe.Subscription;
await logit.now("subscriptions", {
event: "Subscription paused",
description: `Customer ${sub.customer} paused`,
icon: "⏸️",
notify: true,
metadata: { subscriptionId: sub.id },
});
break;
}
}
return new Response("ok");
}
In your Stripe Dashboard → Webhooks, add these events:
customer.subscription.createdcustomer.subscription.updatedcustomer.subscription.deletedcustomer.subscription.pausedcustomer.subscription.resumedUse a dedicated churn channel for cancellations with notify: true. This way you see new revenue in subscriptions and churn in churn — both with instant alerts.
Try LogIt free
7-day trial. No credit card required.