# How to calculate your monthly churn from Stripe

> Who counts as a paying customer, why a 90-day cohort beats last month, and the formula that turns it into a monthly rate. With a script.

By Luis Miguel, founder of TrustChurn. Published 2026-10-31.
Web version: https://trustchurn.com/blog/how-to-calculate-churn-from-stripe

To calculate your monthly churn from Stripe, take the customers who were paying 90 days ago, count how many of them still pay today, and convert that share to a monthly rate with `1 - kept^(1/3)`. If 47 of 50 customers are still paying, you kept 94%, and your monthly churn is 2.0%. Further down are the edge cases and a short script that does it with the Stripe API.

The textbook formula is customers lost this month divided by customers at the start of the month. Try it on a real Stripe account and three questions come up before you get a number: who counts as a customer, which month, and what to do with people who joined and left inside it. Every number on TrustChurn is calculated the way described here, and the formulas are public on the [methodology page](https://trustchurn.com/methodology).

## Who counts as a paying customer?

Stripe has customers and subscriptions, and neither is the same as "someone who pays you". A Stripe customer can exist with no subscription at all, and one person can have three subscriptions.

A paying customer, for churn, is a Stripe customer with at least one subscription that:

- is `active` or `past_due` (past due still counts: they haven't left, their card failed),
- is out of its trial,
- is worth more than zero after discounts, so a free plan or a 100% coupon doesn't count.

A customer with several subscriptions counts once. If you count subscriptions instead, a customer who switches plans by canceling one subscription and starting another looks like a loss and a new signup on the same day, and your churn goes up for no reason.

Statuses like `paused`, `unpaid`, `incomplete` and `canceled` don't count as paying. A paused subscription isn't billing anyone, and an unpaid one has stopped trying to collect.

## Why follow a group instead of a month?

Instead of "lost this month over customers at the start", take everyone who was paying on a given day and check how many of them still pay today. That's a cohort. It answers the third question for you: customers who joined after the start day aren't in the group, so someone who signed up and left two weeks later doesn't push your churn up or down.

We use 90 days. A single month is too short when you're small, since one cancellation more or less moves everything ([churn with 20 customers](https://trustchurn.com/blog/churn-with-few-customers) shows how much). Much longer than a quarter and the number hides what changed recently, like a pricing change or a bad release.

Say 50 customers were paying 90 days ago and 47 of them still pay today. You kept 94% of them in 90 days.

## How do you turn 90 days into a monthly rate?

People compare churn per month, so the 90-day number needs converting. The obvious shortcut is to divide the loss by three: 6% over 90 days, so 2.0% a month. That's close at low churn and drifts as churn grows, because each month you lose a share of a smaller group.

The exact conversion is:

```text
monthly churn = 1 - kept^(30 / days)
```

Over 90 days that's `1 - kept^(1/3)`. With 94% kept it gives 2.0%, the same as dividing. With 70% kept, dividing says 10.0% and the real monthly rate is 11.2%.

The same formula works for any window. A SaaS with 45 days of history can use `1 - kept^(30/45)`. Below 30 days there isn't enough to say anything, which is why TrustChurn doesn't show a churn number until a SaaS has 30 days of paying customers.

## Which cases change the number?

**Customers who leave and come back.** If someone cancels and starts a new subscription inside the window, they were paying at the start and they're paying today. They count as kept. Counting by customer handles this on its own, which is another reason not to count subscriptions.

**Plan changes.** An upgrade or a downgrade inside the same subscription doesn't change anything for customer churn: they were paying and they still are. It does change revenue churn ([customer churn vs revenue churn](https://trustchurn.com/blog/customer-churn-vs-revenue-churn)).

**Annual plans.** A customer on a yearly plan can only leave at renewal. If their renewal didn't fall inside your 90 days, they couldn't have left, and leaving them in the group makes your churn look better than it is. We take them out unless their renewal was in the window ([how annual plans flatter your churn](https://trustchurn.com/blog/annual-plans-and-churn)).

**Refunds and immediate cancellations.** A customer who was paying at the start and whose subscription ended early, refund or not, is gone. They count as lost.

**Failed payments.** A customer in `past_due` is still in. If every retry fails and the subscription is canceled, they're out, even though they never decided to leave. Look at those separately ([failed payments and expiring cards](https://trustchurn.com/blog/failed-payments-stripe)).

**Currencies.** If you charge in more than one currency, convert amounts to one before you calculate anything with MRR. It doesn't matter for customer churn, which only counts people.

## Doing it yourself with the API

The data is all in Stripe. For each subscription, canceled ones included, you need its customer, status, start date, end date, trial end and price. A short script with the API gets you all of it:

```ts
import Stripe from "stripe";

const stripe = new Stripe(process.env.STRIPE_KEY!);
const day = (d: Date) => Math.floor(d.getTime() / 1000);
const start = day(new Date(Date.now() - 90 * 86400_000));
const now = day(new Date());

// Was this subscription paying at time t? (Discounts left out for brevity.)
const payingAt = (s: Stripe.Subscription, t: number) =>
  s.start_date <= t &&
  (s.ended_at === null || s.ended_at > t) &&
  (s.trial_end === null || s.trial_end <= t) &&
  s.items.data.some((i) => (i.price.unit_amount ?? 0) * (i.quantity ?? 1) > 0);

const then = new Set<string>();
const today = new Set<string>();
for await (const s of stripe.subscriptions.list({ status: "all", limit: 100 })) {
  const customer = typeof s.customer === "string" ? s.customer : s.customer.id;
  if (payingAt(s, start)) then.add(customer);
  if (payingAt(s, now)) today.add(customer);
}

const kept = [...then].filter((c) => today.has(c)).length / then.size;
console.log(`kept ${(kept * 100).toFixed(0)}%, monthly churn ${((1 - kept ** (1 / 3)) * 100).toFixed(1)}%`);
```

A restricted key with read access to subscriptions is enough to run it ([what a read-only restricted key can do](https://trustchurn.com/blog/stripe-restricted-key-read-only)). Before you trust the result, know what the script leaves out: subscription and customer discounts, metered prices, paused subscriptions and the annual plan rule above. That's fine for a first look at a simple account, but if you're going to publish the number, you need those cases handled.

A quick way to check the output: list the customers in `then` who aren't in `today`, and open a few of them in the Dashboard. Each one should be someone who really stopped paying.

## Why won't it match Stripe's churn rate?

Stripe's own Billing analytics also shows a churn rate, and it will probably differ from yours. It puts new signups in the denominator and looks at a single 30-day window, among other things, and the [differences are written up here](https://trustchurn.com/blog/stripe-churn-rate-doesnt-match).

## Letting TrustChurn do it

If you'd rather skip the script, connect Stripe with a read-only restricted key and TrustChurn calculates this every day, edge cases included. The number goes on a public profile and in a ranking where every SaaS is measured the same way.

## FAQ

### Should past due customers count as churned?

No. A `past_due` subscription means a payment failed and Stripe is still trying to collect. The customer hasn't left yet. If all the retries fail and the subscription ends, they count as lost from that day.

### Do free trials count in churn?

A trial isn't a paying customer, so it never enters the group you follow, and one that doesn't convert is a conversion problem. A subscription starts counting once its trial is over and it's worth more than zero.

### Why 90 days and not 30?

With a small number of customers, one month holds very few cancellations, and the result jumps from month to month. A 90-day window holds three months of them, and the formula above converts the result back to a monthly rate so you can still compare it with other monthly numbers.

### Can I calculate churn in a spreadsheet instead?

Yes, if you can get each subscription's customer, status, start date, end date and trial end into it. Mark who was paying on the start day and who's paying today, count the overlap, and apply `1 - kept^(1/3)`. The script above does the same thing in a few lines.

### Is a customer with two subscriptions counted twice?

Once. Churn counts customers, so a customer with two paying subscriptions is one customer. They only count as lost when none of their subscriptions is paying anymore.

---

Keep reading:

- [Why Stripe's churn rate doesn't match yours](https://trustchurn.com/blog/stripe-churn-rate-doesnt-match.md)
- [How annual plans flatter your churn](https://trustchurn.com/blog/annual-plans-and-churn.md)

TrustChurn is a public ranking of SaaS by monthly churn, read from Stripe with a read-only restricted key, not typed by the founder. The formulas are public: https://trustchurn.com/methodology
