Should I vibe code
MRR, churn, forecasting, cohorts, and SaaS financial analytics
MRR isn't a number you read off Stripe. It's a dozen accounting decisions, and you'll make all of them by accident.
?
Their verdict, the Launch price and the build-time estimate come from their entry, MIT-licensed. Checked 2026-08-04.
?
Our verdict, the regret score and everything below it. Editorial and unsponsored — nobody can pay to be moved.
The honest answer
why the verdict is what it is
Stripe's API is excellent and a dashboard that says MRR is $8,412 will exist by Sunday lunchtime. The problem is that MRR is not a field you read, it is a definition you choose, and there are about a dozen decisions inside it: does an annual plan count once in March or a twelfth every month, does a 20% coupon reduce it, is that gross or net of Stripe's fee and of tax, what happens to a subscription that is past_due but not yet cancelled, and which day's FX rate turned €49 into dollars. Every one of those has a defensible answer, you will pick one by accident, and the chart will look completely plausible either way. That is the whole entry. Nobody gets paged over a bad MRR number — you just quote it in a board update, restate it two months later when you fix the annual-plan handling, and spend the meeting explaining why growth used to look different.
What actually breaks
not "if". the specific failures.
- Annual plans, which are the single most common way homemade MRR is wrong — one $1,188 charge booked as $1,188 of monthly recurring revenue makes January look like the best month you have ever had
- Retroactive edits, because if you compute history live from Stripe's current state, one refund issued today silently rewrites the chart you screenshotted in March
- The churn denominator: customers at the start of the period, or the average, or only those who came up for renewal — the three formulas disagree by roughly a factor of two and every SaaS blog picks a different one
- Upgrades and downgrades mid-period, where proration means the amount charged and the amount recurring are two different numbers and only one of them belongs in MRR
- Failed payments, since a subscription sits in past_due for days or weeks and is neither active revenue nor churn until the dunning sequence gives up
- Multi-currency, which needs a rate and a date, and you will pick today's rate for a charge made in 2024 without noticing
- Test-mode data reaching the production table, where a single seeded $2,000 subscription becomes a growth story
- The Stripe key itself, which the setup guide will happily have you create with full access when all this needs is read
Is that you?
the verdict is a default, not a law
- You are the only person who reads it and you already know which figures you trust
- Every metric on the page names its definition, in text, next to the number
- The monthly total reconciles against Stripe's own reporting before anyone quotes it
- The Stripe key is a restricted read-only key and you can say so without checking
- The number goes into a board deck, an investor update or a public revenue page
- Annual and monthly plans are added together without normalising the annual ones
- History is recomputed from live Stripe state rather than from stored snapshots
- You cannot say, in one sentence, what your churn denominator is
If you build it anyway
the checklist, then the prompt that enforces it
- Write the definitions down before the queries. One page: what counts as MRR, how annual plans are normalised, whether it is gross or net, what state a customer must be in to count. That page is the product; the chart is a rendering of it.
- Snapshot each period's metrics when the period closes and never recompute them. A dashboard that silently restates last quarter every time someone issues a refund is worse than one that is slightly stale.
- Normalise annual and multi-month plans to a monthly figure at the point of ingestion, and write a test with a real annual invoice in it.
- Use a Stripe restricted key with read-only scopes. Nothing in an analytics dashboard needs the ability to issue a refund, and a full key in a hobby project's env file is the only part of this that can cost real money.
- Mirror Stripe into your own store idempotently on the event id rather than querying the API per page load — you want a stable local history you can re-derive from, and you want it to survive Stripe rate limits.
- Print the currency conversion policy and store the rate used with each charge. Rates fetched at render time make history move.
- Reconcile your monthly total against Stripe's own reports on a schedule and alert on divergence past a threshold you set in advance, because nothing else here will tell you the number is wrong.
- Keep test-mode and live-mode data in separate tables, not separate config values.
I am building a subscription-metrics dashboard over Stripe. The risk here is a
confident wrong number, so work in this order and argue with me if I reorder it.
1. Before any code, write definitions.md: what counts as MRR, how annual and
multi-month plans are normalised to a month, gross or net of fees and tax,
how discounts apply, which subscription statuses count, and the exact churn
formula including its denominator. Ask me to approve it.
2. Use a Stripe restricted key with read-only scopes. If any step seems to need
write access, stop and tell me instead of asking for a full key.
3. Ingest into my own tables, idempotently on the Stripe event id, enforced by a
unique constraint. Keep the raw payloads. Every metric is a query over that
store, never a live API call at render time.
4. Normalise annual plans at ingestion. Write a failing test first using a real
annual invoice, then make it pass. Do the same for a mid-period upgrade with
proration and for a coupon.
5. Snapshot each closed period's metrics into an immutable table. Never let a
refund or a backdated edit rewrite a month that has already been reported.
If a restatement is needed, record it as a restatement.
6. Store the FX rate and its date with every non-USD charge. Never convert with
today's rate at query time.
7. Separate test-mode and live-mode data at the table level.
8. Render every metric with its definition next to it, as text. No unlabelled
MRR, no unlabelled churn.
9. Build a reconciliation job that compares the monthly total to Stripe's own
reporting and alerts on divergence beyond a threshold I choose.
10. Do not implement forecasting, LTV or cohort projections until the base
metrics reconcile. A forecast built on a wrong MRR is confidently wrong
twelve months into the future.
11. In the README, state plainly which figures are safe to quote externally and
which are not.That one keeps you out of trouble. For the prompt that actually builds it, canivibecodeit.com has one.
their build prompt ↗Or don’t build it
the boring option, and the way back out
The moment someone outside your own head reads the number — a co-founder, a board, an acquirer, a public dashboard. $75 a month buys a definition of MRR that has been argued over by people who do this full time, and, more usefully, one that everyone you show it to already recognises. Explaining your own churn formula in a diligence call is a worse afternoon than the subscription.
$75/mo is cheaper than your weekend.
Keep the Stripe mirror and the period snapshots in your own tables, both with stable ids and both re-derivable. Stripe remains the system of record, so the raw side of this is never really lost — you can always re-ingest. What you cannot recover is the reasoning: which definition produced the numbers you have already told people. Put definitions.md in the repo, version it, and record which version each snapshot was computed under. That single file is the difference between migrating to Baremetrics or ChartMogul and quietly starting your revenue history over.
Mirrors a Stripe account into Postgres, which is the ingestion half of this problem already solved.
Open-source BI you can point at that Postgres mirror instead of writing a dashboard.
Open-source product analytics with revenue tracking, self-hostable.
Questions
How is this different from the DataFast entry, which is also Stripe charts?
DataFast attributes revenue to a marketing channel, and attribution has no ground truth — it is a model. This is the opposite: MRR does have a right answer, several organisations have written it down, and yours will differ from theirs in ways you can actually be corrected on. A wrong attribution figure moves an ad budget. A wrong MRR figure goes in a fundraising deck.
What is the single most likely bug?
Annual plans. A $1,188 yearly subscription charged in January either adds $99 to MRR every month or $1,188 to January, and the naive version of this code does the second. It does not error, the chart looks like a hockey stick, and you find out when someone asks why revenue fell 90% in February.
Can I just use a read-only Stripe key and skip the local database?
You can, and it works until you have enough history that every page load is a hundred paginated API calls, or until Stripe rate-limits you mid-demo. The bigger problem is that querying live state means your history changes whenever the past does — a refund issued today retroactively edits a month you already reported.
Every week, someone ships something they shouldn’t have.
New verdicts, the worst thing that landed in the trap, and the occasional incident report. No other email, ever.
Attribution is not a measurement, it is an opinion. Yours will be wrong in a way that looks fine.
Two billing systems, one customer, two email addresses. That is not a charting bug, it is revenue counted twice.
Fathom counts pageviews. This one knows who Sarah is, where she works and everything she clicked since March.
last reviewed 2026-08-04 · verdict is editorial and unsponsored · shared entry data from canivibecodeit under MIT · not legal advice