Guides / Payments

How to Monitor Stripe Subscriptions in Real Time

5 min read·Payments

The full subscription lifecycle

Stripe fires a different webhook for every subscription state change. Here's one handler that covers them all:

ts
// 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");
}

Stripe webhook events to subscribe to

In your Stripe Dashboard → Webhooks, add these events:

  • customer.subscription.created
  • customer.subscription.updated
  • customer.subscription.deleted
  • customer.subscription.paused
  • customer.subscription.resumed

Tip: separate channels for churn

Use 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.

Start free