Reconciling MRR Against Recognised Revenue
MRR and recognised revenue are different numbers by construction, and they should be. MRR is a normalised run rate that ignores when cash arrives and spreads annual plans evenly; recognised revenue follows accounting rules about when a performance obligation is satisfied. The reconciliation is not an attempt to make them equal — it is a control that proves every difference between them has a name. This guide sits under Billing Analytics & Revenue Metrics and covers building that bridge.
Without it, the two numbers drift and nobody notices until an auditor or an investor asks why the revenue line and the growth chart tell different stories. With it, a drift shows up within a month as an unexplained residual, and residuals almost always point at a real defect — a subscription in the wrong currency, a plan whose amount was stored in the wrong unit, or a drawdown dated to the wrong period.
The reconciliation is also the cheapest way to discover that a metric definition has quietly changed. A residual that was stable for six months and jumps in one is a stronger signal than any dashboard, because it compares two independently computed views of the same underlying business.
Trade-offs
| Reconciliation approach | Effort | Detects definition drift | Detects data bugs | Audit value |
|---|---|---|---|---|
| None | Zero | No | No | None |
| Eyeball the two totals | Minimal | Sometimes | Rarely | Low |
| Bridge with named items | Moderate, monthly | Yes | Yes | High |
| Account-level reconciliation | High | Yes | Precisely | Very high |
The account-level row is worth aspiring to but not starting with. Build the aggregate bridge first, get the residual small and stable, and only then drill to account level when a residual appears — at that point the drill-down is a targeted investigation rather than a monthly chore.
Step-by-Step Implementation
1. Select the right ledger accounts
Recurring revenue must be posted to accounts that distinguish it from one-off charges, professional services, and usage overage. If the ledger lumps them together, the bridge cannot be built and the first task is a chart-of-accounts change rather than a query.
-- Revenue recognised in a month, split by the categories the bridge needs.
SELECT date_trunc('month', posted_at) AS month,
account_code,
sum(amount_minor) FILTER (WHERE side = 'credit')
- sum(amount_minor) FILTER (WHERE side = 'debit') AS recognised_minor
FROM ledger_entries
WHERE account_code IN ('4000_subscription', '4010_usage', '4020_setup', '4900_credits')
GROUP BY 1, 2;
2. Quantify each reconciling item
Mid-month timing. MRR counts a subscription that started on the 20th at its full monthly amount; the ledger recognises eleven days of it. The adjustment is the sum of unrecognised portions for subscriptions that started or ended mid-month, and it is usually the largest single item.
One-off charges. Setup fees, professional services, and one-time overage are recognised revenue that never appears in MRR. They are additive to the bridge and should come straight from their own ledger accounts.
Usage overage. Depending on your definition, metered consumption may be partly in MRR and partly not. Whatever the rule, the bridge needs the portion excluded from MRR as an explicit item rather than absorbed into the residual.
Credits and concessions. A credit note reduces recognised revenue and, by the definitional rule that credits do not change the recurring amount, leaves MRR untouched. That difference is a named item, not a discrepancy.
3. Compute the residual
4. Investigate by account
When a residual crosses the threshold, the fastest route to the cause is a per-account comparison for the month: MRR contribution versus recognised revenue, sorted by absolute difference. The largest few rows almost always contain the answer, and the pattern identifies the class of bug.
-- Accounts contributing most to the residual this month.
SELECT a.account_id,
a.mrr_minor,
coalesce(r.recognised_minor, 0) AS recognised_minor,
a.mrr_minor - coalesce(r.recognised_minor, 0) AS diff_minor
FROM month_mrr_by_account a
LEFT JOIN month_recognised_by_account r USING (account_id)
ORDER BY abs(a.mrr_minor - coalesce(r.recognised_minor, 0)) DESC
LIMIT 25;
5. Record the signed-off bridge
The output of the process is a stored artefact: the month, each named item and its amount, the residual, and who signed it off. Storing it makes the control auditable and lets you chart the residual over time, which is how you notice a slow drift that never crosses the threshold in any single month.
What Residuals Usually Mean
After a few months, residuals stop being mysterious and start falling into recognisable categories, each with a characteristic signature.
A units error shows up as a residual that is a clean multiple — exactly 100× or 1/100 of an expected figure — and is almost always one plan or one currency where an amount was stored in major units among minor ones. It is the easiest to spot and the most embarrassing to find late.
A currency error shows up as a residual roughly proportional to one currency’s share of the base, and appears the month a rate changes materially. The cause is usually a metric converting at a live rate while the ledger uses a fixed month-end rate, or vice versa.
A dating error shows up as a residual that reverses next month: too high in one period and too low in the next by a similar amount. That signature points at something recognised or drawn down in the wrong period — a cycle-close job that ran after midnight in the wrong time zone, or a commitment drawdown dated to the period start when its usage belongs to the period end.
A status error shows up as a persistent, slowly growing residual with no reversal, and comes from a subscription status that one side counts and the other does not — typically past_due or paused. It never self-corrects, which is what distinguishes it from a dating error.
A scope error shows up as a step change coinciding with a product launch, when a new revenue stream posts to the ledger but was never added to the metric’s definition or its account list. This is the residual most likely to be dismissed as “the new product”, and it deserves the opposite reaction: the bridge is doing exactly its job by flagging that the definition needs updating.
Making the Bridge Routine Rather Than Heroic
A reconciliation that requires a day of someone’s time each month will be skipped the first time the month is busy, which is exactly the month it was most likely to catch something. Three practices keep it to under an hour.
Automate the item computation, not just the totals. Each reconciling item — mid-month timing, one-off charges, overage, credits — should be a query that runs on a schedule and writes its result into the stored bridge, so the monthly work is reviewing numbers rather than producing them. The residual then appears without anyone assembling it.
Chart the residual as a time series and put it somewhere both finance and engineering see. A single number in a spreadsheet is reviewed once; a line on a dashboard is noticed when it moves. The trend also makes the difference between a one-off and a drift visible immediately, which is the distinction that decides whether to investigate now or at close.
Attach the investigation output to the bridge record. When a residual is explained — “a plan created in March stored its amount in major units” — record the explanation and the fix alongside that month’s bridge. Six months later, when a similar signature appears, the previous investigation is the fastest route to the answer, and without a record it is institutional memory that leaves with whoever did the work.
Set the review as a calendar commitment with two named people rather than a task in a backlog. The control’s value comes from being run every month without exception; a reconciliation performed in nine months out of twelve provides considerably less than three-quarters of the assurance, because the gaps are where a slow drift establishes itself.
Verification & Testing
Test the bridge with a synthetic month where every item is known: two subscriptions starting mid-month, one setup fee, one credit note, and one overage line. Assert the residual is exactly zero. A synthetic case that does not reconcile perfectly means the bridge’s own arithmetic is wrong, and no amount of investigation on real data will reveal that.
Then inject each error class deliberately and assert the residual appears with the expected sign and magnitude: multiply one plan’s amount by 100, convert one currency at the wrong rate, shift one drawdown by a month, and include one past_due subscription on only one side. Confirming that the control detects each class is what justifies trusting it when it stays quiet.
Finally, assert the threshold logic itself, including the boundary. A residual exactly at the blocking threshold should block, and a test that fixes this behaviour prevents a later “small” relaxation from silently disabling the control.
Gotchas & Production Pitfalls
- Comparing MRR to cash collected. They share almost nothing. The comparison must be against recognised revenue, from the ledger, for the same period.
- A residual that becomes normal. Once a persistent unexplained difference is accepted, the control is dead. Name it or fix it, but do not carry it.
- Different month boundaries on the two sides. A metric on UTC months and a ledger on local-time months will never reconcile, and the difference looks like a mystery rather than a configuration mismatch.
- Credits netted into revenue accounts. If credit notes post to the same account as revenue, the bridge cannot separate them and the item disappears into the residual.
- Reconciling only the total. Two compensating errors net to zero at the top and are individually large. Reconcile by category, not just in aggregate.
Frequently Asked Questions
How large should the residual be? Small and stable. A tenth of a percent of MRR is a reasonable target for a mature reconciliation; the absolute value matters less than whether it is explained and whether it is trending.
Who owns the bridge? Jointly. Finance owns the ledger side and the sign-off; engineering owns the metric side and the investigation. A bridge owned entirely by one side becomes a report the other does not read.
Should the bridge run before or after the ledger closes? Draft it before close so discrepancies can be corrected in the period they belong to, then finalise after close using the final ledger figures. Running it only afterwards turns every finding into a restatement.
Does this replace the audit? No, but it makes one dramatically easier. A stored monthly bridge with named items and a signed-off residual is exactly the evidence an auditor asks for when testing revenue completeness.