---
id: saas/mrr-movimentos-nrr-grr
industry: SaaS and technology
usage: widely used
effort: high
grain: Customer x month (end-of-month state) and customer x product x month
testado_com_agente: Oct 2026
---

# MRR and its movements, with NRR and GRR

How much MRR came in, expanded, contracted, churned and returned each month, by customer, matching finance and without rewriting the past.

## Executive summary

**For:** SaaS companies that need to explain month-over-month MRR changes to leadership and the board.

**What it solves**

- The report's MRR matches billing and finance at close.
- Every real of change has a category: new, expansion, contraction, churn or reactivation.
- A closed month's number doesn't change after it has been reported.

**Why it matters:** Billing only stores the subscription's current state, so today's MRR rewrites the past and churn gets mixed up with downgrades. Without the historical series there is no NRR, no GRR and no answer to why revenue dropped. It is the foundation for forecasting, expansion commissions and the health score.

**Business questions it answers**

- How much MRR did we lose to churn in August, and how much was just contraction?
- What were NRR and GRR over the last 12 months?
- How much of the quarter's new revenue came from the base and how much from new customers?
- Which product concentrates revenue churn?
- Which customers started paying again after canceling?

**What you need**

- Billing connected to Nekt (subscriptions, items, prices and invoices)
- A daily history of subscription state, stored starting now
- The rule agreed with finance for annual plans, discounts, usage, delinquency and currency

**What you get**

- A customer x month table with starting MRR, ending MRR and the classified movement
- NRR, GRR and churn by month, quarter and trailing 12 months
- An MRR waterfall for the close and for the board
- An AI agent that answers revenue questions with the right rule

## Detailed

### The problem

Billing shows the subscription as it is now: whoever canceled shows up with zero MRR and whoever changed plans shows up with only the new plan. Summing the change log counts movement that came and went in the same month, and comparing subscriptions instead of customers calls a plan change churn. The result is a different NRR in every spreadsheet and a closed month that changes after it has been reported.

### Business rules

1. **A movement is the difference between two end-of-month snapshots.** Compute each customer's MRR on the last day of the month and compare it with the last day of the previous month. Each customer-month falls into a single movement. The raw change log counts back-and-forth changes within the month and, in one real base, inflated movement by about 18%. The month's snapshot uses the last day of the month for every segment, never the last day each segment had a row. (Avoid: Summing the month's subscription change event log.)
2. **The grain is the customer, not the subscription.** Churn is a customer who had MRR at the end of the previous month and ends the month with no active subscription. A plan change, renewal or subscription swap for the same customer is expansion, contraction or nothing. Migrating the same customer from one product to another can still count as churn on the source and new on the destination in the per-product view, but must be flagged separately so it doesn't appear as logo loss. Economic-group consolidation follows the rule the workspace already has: if the rule is not to consolidate, each billing account is a customer and only the groups listed in the exceptions are merged. (Avoid: Counting each canceled subscription as a churn.)
3. **MRR is monthly-normalized and no active item is left at zero.** Annual, semiannual, quarterly or non-standard cycle plans go in divided by the months in the cycle (in Stripe, price.unit_amount x quantity divided by the months of price.recurring.interval x interval_count). Prices with billing_scheme = tiered or usage_type = metered have no unit_amount, and the formula returns zero with no error: value that item by the total of the lines on its latest paid invoice (a tiered item produces several lines in the same period, sum them all) or sum the tiers for the contracted quantity. Before moving on, list the items on active subscriptions that ended up with zero MRR: the list must be empty or every case explained. When the source is the invoice, allocate the recurring amount across the accrual months it covers. (Avoid: Using unit_amount x quantity for every item and letting tiered or usage prices count as zero without noticing.)
4. **Only recurring revenue goes into MRR.** Implementation, setup, one-off credit purchases (one_time price), prorations, one-off services, marketplace fees and partner revenue stay out. Usage-based billing (metered price) only counts in MRR up to the contractual minimum; variable usage above it goes to its own usage revenue column, otherwise every swing in consumption becomes expansion or contraction. A recurring discount reduces MRR; declare when a temporary discount starts to count (some only consider open-ended discounts or those lasting more than 3 months). (Avoid: Summing the whole invoice, with implementation, one-off credits and variable usage.)
5. **Status decides who is in the snapshot.** Active subscriptions go in. Trialing is not MRR. Canceled and incomplete_expired go out, and the filter is mandatory because a canceled subscription still has items and price filled in. Past_due is a business decision: the safest is to keep it in MRR flagged as at risk until it becomes canceled or unpaid. Removing past_due from MRR creates churn and reactivation that never happened when the invoice gets paid. Unpaid is also a declared decision: state whether it leaves MRR and check it against the finance report's criterion, which often keeps unpaid, or the reconciliation won't close. A freeze shows up in Stripe as status paused or pause_collection filled in on the subscription: classify it as a freeze, not as churn. (Avoid: Summing items from every subscription, or removing past_due and adding it back the next month.)
6. **Churn uses the last MRR before leaving.** On cancellation, the customer's current MRR goes to zero. The amount lost is the customer's MRR at the end of the previous month, or the last known value before canceled_at. Zero MRR never means a free customer: it means a customer who left. (Avoid: Reading the canceled subscription's current MRR and finding zero.)
7. **Reactivation is someone who paid before and returns.** A customer with zero MRR at the end of the previous month and more than zero now is new if they never paid and a reactivation if they had MRR in some earlier month. Reactivation doesn't go into the cohort's NRR, because the customer wasn't in the starting base. (Avoid: Counting the returning customer as a new customer.)
8. **Keep the history yourself.** Billing exposes the current state, and Stripe's events API only keeps the last few days. A change in quantity or plan on the same subscription disappears. Write a daily snapshot of subscriptions and items, or a history with valid from and valid to, from day one. For the months before the history starts, rebuild from paid invoice lines with a recurring price and no proration, counting each line in the month whose last day falls inside its service period (period.start to period.end). The first month with a snapshot compares two different sources: flag it as the seam month and keep its movement out of the board deck. (Avoid: Computing past months' MRR with today's subscriptions table.)
9. **A closed month is frozen.** After the close, write the month's result as a provisional version and allow a single correction, which becomes final. Later retroactive corrections go in as an adjustment in the month they were discovered, with the reference month recorded, never rewriting the number already reported. (Avoid: Recomputing every month on every run.)
10. **Never sum currencies.** Convert everything to the reporting currency at a declared rate and use the same fixed rate across the whole movement series: with a monthly rate, a foreign-currency customer who didn't change plans shows up as expansion or contraction. If you need the view at realized exchange rates, show the FX effect on a separate line. Check whether the converted column comes through null for foreign currency before summing. (Avoid: Summing BRL and USD in the same column.)

### How each customer-month is classified

Comparing the customer's MRR at the end of the previous month (start) with the end of the month (end).

- **start = 0, end > 0, never paid** → New
- **start = 0, end > 0, paid before** → Reactivation
- **start > 0, end > start** → Expansion (`end - start`)
- **start > 0, 0 < end < start** → Contraction (`start - end`)
- **start > 0, end = 0** → Churn (`start`)
- **start > 0, end = start** → Stable
- **subscription with status paused or pause_collection filled in** → Freeze, separate from churn
- **start = 0, end = 0** → Outside the base, no row and no category

### Definitions

| Metric | Definition |
|---|---|
| Customer MRR | sum, on the last day of the month, of the monthly-normalized amount of recurring items on the customer's active subscriptions, net of recurring discounts; a tiered or usage item is worth what its latest paid invoice charged, up to the contractual minimum, and variable usage goes in its own column |
| Starting MRR | customers' MRR on the last day of the previous month |
| New MRR | end-of-month MRR of customers who had never paid |
| Expansion | end - start, only where end > start > 0, by customer and month |
| Contraction | start - end, only where start > end > 0, by customer and month |
| MRR churn | starting MRR of customers who end the month with zero MRR; logo churn is the count of those customers |
| Reactivation | end-of-month MRR of customers who had zero MRR at the start and paid in some earlier month |
| GRR | (starting MRR - contraction - churn) / starting MRR, only with customers in the starting base; never exceeds 100% |
| NRR | (starting MRR + expansion - contraction - churn) / starting MRR, only with customers in the starting base, with no new or reactivation; over 12 months, compare the same group of customers at the start and end of the window |

### Pitfalls

- **Tiered prices counted as zero.** Items with tiered or metered prices have no unit_amount, and the price x quantity formula counted them as zero. A few of those items accounted for a meaningful share of MRR, and the total came out several points below finance with no error in the query. How to avoid: Value those items from the latest paid invoice and list active items with zero MRR before closing the month.
- **Bulk cancellation.** Subscriptions with no payment for months were canceled all at once and all the churn landed in a single month. How to avoid: Close the exit at the earlier of the cancellation date and the last payment plus one cycle.
- **Phantom reactivations and orphan churn.** A model based on the product screen, not on billing, created activations and reactivations that were never charged and churn from customers with no billed MRR. How to avoid: Rebuild the start and end state from billing only, with valid from and valid to.
- **Rewritten cycle date.** When a subscription recovers from a failed charge, billing pushes the cycle start date forward, and long-standing customers looked new. How to avoid: Anchor the customer's start on the first confirmed payment, never on the cycle date.
- **Customer counted several times.** The same group with several CNPJs (company tax IDs) or several paying sub-accounts showed up as several active customers and as several churns. How to avoid: Follow the workspace's consolidation rule: if it consolidates, use the canonical customer and the CNPJ with the highest MRR as the primary one; if it doesn't, merge only the groups listed in the exceptions.
- **The number changed after the board meeting.** The table was recomputed on every run and the MRR of closed months changed after being presented. How to avoid: Freeze the closed month and record adjustments in the month they were discovered.

Validated on real data: In Nekt's own Stripe account, more than 8 in 10 canceled subscriptions still carry items with a price above zero, so summing items without filtering status puts customers who already left into MRR. And some subscriptions that went through past_due returned to active later, which is why removing past_due from MRR would create churn and reactivation that never happened.

### Acceptance test

- [ ] Each customer-month has exactly one category, and starting MRR + new + reactivation + expansion - contraction - churn = ending MRR, with no difference.
- [ ] No item on an active subscription in the month's snapshot is left with zero MRR; tiered and metered items carry a value taken from invoices.
- [ ] A closed month's total MRR matches the billing system's or finance's MRR report within 1%, after aligning the unpaid criterion, the exchange rate and customers billed outside the billing system; every remaining difference is explained.
- [ ] GRR never exceeds 100% and NRR doesn't change when new customers are added to the month.
- [ ] Running the pipeline again doesn't change any number for a month with a final version.

### Context document

```text
MRR movements
- Customer MRR = monthly-normalized amount of recurring items on active subscriptions on the last day of the month, net of recurring discounts. Cycles longer than a month divided by the months in the cycle. A tiered or usage item is worth what its latest paid invoice charged.
- Outside MRR: implementation, setup, one-off credits (one_time price), prorations, one-off services, marketplace fees and partner revenue. Variable usage above the contractual minimum goes in the usage revenue column.
- Status: active goes in; trialing, canceled and incomplete_expired don't. Past_due stays in MRR flagged as at risk until it becomes canceled or unpaid. Unpaid: <in or out>. Paused or pause_collection filled in = freeze, not churn.
- Movement = the customer's MRR at month end compared with the end of the previous month. One movement per customer-month: new, reactivation, expansion, contraction, churn or stable. Zero at start and end produces no row.
- Churn is at the customer level: only when the customer ends the month with no active subscription. A plan change is not churn. Churn amount = the customer's MRR at the end of the previous month.
- Zero MRR doesn't mean a free customer. It means a customer who left.
- Reactivation = a customer with zero MRR at the start who paid before. It doesn't go into NRR.
- GRR = (starting MRR - contraction - churn) / starting MRR. NRR = (starting MRR + expansion - contraction - churn) / starting MRR. Only customers in the starting base.
- Never sum different currencies. Reporting currency: <currency>, fixed rate for the whole series: <declared rate>.
- Months before <history start> come from paid invoices by service period. <seam month> is the seam month between sources and doesn't go to the board.
- Economic groups: <consolidated or not consolidated>, per the workspace rule.
- Closed months come from the frozen table. Don't recompute or quote a closed month's number from another source.
Per-client exceptions:
- <client>: migration from product <source> to <destination> within 30 days is flagged as a migration, not as logo churn.
- <client>: economic group with several CNPJs (company tax IDs) consolidated under CNPJ <primary>.
- <client>: billed outside the billing system; add <recurring amount> to MRR from <month>.
- <client>: usage price with a contractual minimum of <amount>, which is the part that counts in MRR.
```

### Variations

- **Per-product view:** The same rule at the customer x product x month grain. Cross-sell shows up as expansion with a new-product flag.
- **Billing with no history:** Rebuild the past from paid invoice lines by service period, start the daily subscription snapshot today and flag the seam month between the two sources.
- **MRR from the CRM:** When the contract lives in the CRM, use the recurring amount from the won deal's line items, never the deal's total amount field, and check it against billing.
