Guides / SaaS

How to Track SaaS Churn Events

3 min read·SaaS

Why Track Churn Events

Churn is easier to fight when you catch it immediately. A cancellation notification gives you a window to send a win-back email. An expiration without conversion tells you the trial didn't land.

Subscription cancelled (Stripe)

ts
case "customer.subscription.deleted": {
  const sub = event.data.object;
  const user = await getUserByStripeCustomerId(String(sub.customer));

  await logit.now("churn", {
    event: "Subscription cancelled",
    description: `${user?.email ?? sub.customer} cancelled`,
    icon: "😢",
    notify: true,
    tags: {
      plan: String(sub.items.data[0]?.price.nickname ?? "unknown"),
      reason: sub.cancellation_details?.reason ?? "unknown",
    },
    metadata: {
      subscriptionId: sub.id,
      customerId: String(sub.customer),
      email: user?.email,
      reason: sub.cancellation_details?.reason,
      feedback: sub.cancellation_details?.feedback,
    },
  });
  break;
}

Downgrade

ts
await logit.now("churn", {
  event: "Plan downgraded",
  description: `${user.email}: ${oldPlan}${newPlan}`,
  icon: "📉",
  notify: true,
  tags: { from: oldPlan, to: newPlan },
  metadata: { userId: user.id, email: user.email, oldPlan, newPlan },
});

Trial expired without converting

Run from your trial-expiry cron job:

ts
await logit.now("churn", {
  event: "Trial expired — no conversion",
  description: user.email,
  icon: "⏰",
  notify: false,
  tags: { type: "trial-expired", daysOnTrial: String(trialDays) },
  metadata: { userId: user.id, email: user.email, trialStarted: user.trialStartedAt },
});

Tip

Log both the reason and feedback fields from Stripe cancellations — they often contain the clearest signal for product decisions.

Try LogIt free

7-day trial. No credit card required.

Start free