Guides / SaaS
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.
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;
}
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 },
});
Run from your trial-expiry cron job:
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 },
});
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.