Recurring Revenue in GA4: Why MRR Never Adds Up
Two people open a dashboard on the same morning. One reads monthly recurring revenue from the billing system. The other reads Purchase revenue in GA4. The figures differ by a few thousand pounds, and neither of them is wrong.
GA4 counts events. A finance system counts contracts. Recurring revenue is a property of an agreement with a start date, a billing period and an end date, and Analytics has no concept of an agreement at all. It knows that a browser reported a purchase on a Tuesday. It does not know what was promised for the next twelve months.
That gap is fixable, but not by hunting for a setting. It is fixed by deciding which questions belong in GA4 and which belong in the ledger, then wiring the events so the first set can be answered at all.
What the revenue metrics are actually built from
Every revenue figure in GA4 comes from the parameters on a purchase event. Google's own rule for the value parameter is to set it to the sum of price times quantity for the items, and then a sentence that matters more than it looks: do not include shipping or tax.
So GA4 revenue is net of tax by design. tax and shipping exist as separate optional parameters for exactly that reason. Send the gross invoice amount including VAT and the property reports revenue that is 20 percent high against your accounts, with nothing inside Analytics to flag it, because there is no tax field to reconcile against.
The second trap is item level. Event level revenue reads value and currency. Item level revenue reads price and currency. A purchase with a correct value and no item level price shows the right total and an empty Item revenue column, which reads as a broken report rather than a missing parameter.
Names also differ more than people expect. In GA4's own definitions, Purchases counts purchase, ecommerce_purchase and in_app_purchase. Transactions counts those plus refund, app_store_subscription_renew and app_store_subscription_convert. Ecommerce purchases counts only purchase. Three metric names, three different event sets, and a screenshot of the wrong one explains a lot of arguments.
Note what sits in that list and what does not. Google ships an app subscription renewal event for the app stores. There is no web equivalent. A monthly card charge on your own site is a purchase event or it is nothing.
Three shapes of recurring revenue, one of which fits
A monthly plan charged to a card produces one invoice and one payment each month. Fire one purchase per invoice and GA4 sees a steady line that roughly matches billed revenue, offset by the tax question above.
An annual plan paid upfront produces one payment of 1,200 pounds in month one and nothing for eleven months. GA4 reports a spike. The finance team reports 100 pounds of MRR across the year. Both records are accurate descriptions of different things, and every annual billing business hits this in the first month after launch.
Proration breaks the pattern further. A mid cycle upgrade generates a part month invoice for the difference, a downgrade generates a credit, and seat changes generate small adjustments. Sum twelve months of those events and you get cash collected, not contracted revenue. Feed that number into a board slide as MRR and somebody will ask why it jumps in the months when you ran a promotion.
Four events that make the recurring questions answerable
The event model below covers what a subscription business actually asks. It uses Google's recommended events where they exist and one custom event where they do not.
sign_up at account creation or trial start. Google's recommended trigger is that a user signs up for an account. Add a parameter that separates a trial from a paid start, because a trial that converts and a trial that expires look identical in a sign_up count.
purchase when an invoice is paid. Include transaction_id as the invoice number, value as the net amount, currency as an ISO 4217 code, and items with a price if you want item level revenue.
refund when a credit note is issued. Google requires both transaction_id and value here, which is what lets a refund net off the original purchase rather than sitting as an unexplained negative row.
Your own cancellation event. There is no recommended event for a cancelled subscription, and nothing in the ecommerce set that covers it. Pick a name and a parameter for the reason, and keep it consistent across web and server, because a cancellation event that fires under two names is two half filled reports.
One parameter is worth adding to the purchase event itself: customer_type, with values of new or returning. Google defines returning as a customer who purchased inside a given window, and notes that 540 days is the recommended default but is not mandatory. That single parameter lets you separate first invoices from renewals without a second event, which is the cheapest way to answer the question of whether growth came from acquisition or from the base.
The invoice ID, and the empty string that eats your data
Transaction ID deduplication works for web streams only, not app streams. The ID has to be unique per transaction and must not be reused across users. Retries, webhook replays and a page refresh on the thank you screen then collapse into one purchase instead of three, which is the whole point.
Google's warning deserves its own line. Do not send an empty string as the transaction ID, because Analytics deduplicates every purchase event that carries transaction_id="" into a single event. A billing integration that misses the invoice number on some code path does not lose those payments. It quietly merges them, and the revenue line goes flat while the business grows.
The ID must also omit anything that identifies a customer, so use the invoice number rather than an email address or a customer key.
Failed payments are invisible
If the server only reports invoices that were paid, a failed card charge produces no event at all. The customer enters a dunning sequence, recovers two weeks later, and Analytics shows nothing about the gap. Churn that starts with a declined card looks, in GA4, like a customer who simply stopped logging in.
Retries create attempts, not revenue. A payment processor that retries three times will call the same webhook three times, and the transaction ID is what stops three attempts becoming three purchases.
The other common double count is a purchase fired both by a client side thank you page and by the billing webhook. Two sources of truth, two events, and deduplication saves you only when both carry the same invoice number.
Churn probability measures something else
GA4 has no churn metric. It has a churn probability prediction, defined as the probability that a user who was active within the last seven days will not be active in the next seven. That is a prediction about engagement. A subscriber who pays by direct debit and never opens the product is churn by the model and a healthy account in the ledger.
Purchase probability and predicted revenue have a narrower input than most people assume. Only purchase and in_app_purchase events feed them. A tidy custom renewal event, well modelled and beautifully named, contributes nothing to either prediction, because Google's models are not reading it.
Eligibility is also not automatic. The models need at least 1,000 returning users who triggered the condition and 1,000 who did not inside the qualifying window, and quality is not guaranteed. A property below that volume can be correctly configured and still show nothing.
Retention decides how long a cohort story can be
User level data retention on a standard property is two months or fourteen months. Key events data follows the same period. Other event data can go to 26, 38 or 50 months on 360 only.
The scope of that setting is narrower than the fear it creates. It does not change standard aggregated reports, comparisons included. It applies to explorations and funnel reports, which is precisely where cohort and lifetime questions get answered.
So a subscriber whose second renewal falls nineteen months after signup cannot be measured in Explorations on a standard property. That analysis belongs in the billing system or in BigQuery, where the export keeps the raw events. Also worth checking before promising a two year cohort view, large properties that cross the event volume limit drop to two month retention automatically.
Trials, audiences and the backfill problem
A trial to paid funnel spans days or weeks, which is where session scoped thinking falls apart. Build it user scoped, with a window long enough to cover your actual conversion lag rather than the default.
Audiences have their own limits. Membership duration caps at 540 days, and the reset on new activity option caps it at fourteen months. Creation backfills the last 30 days, and Google notes that backfill applies to Google Ads audiences only. An audience for accounts whose trial started 45 days ago and never converted has to exist before those accounts reach day 45. Built late, it starts empty and looks broken.
Where the MRR number should come from
The billing system owns the numbers that survive a cancellation: MRR, ARR, churn rate, net revenue retention, expansion and contraction. Those are contract facts. Analytics sees a browser session, and the session ends long before the renewal.
GA4 owns a different set, and it owns them well. Which channel produced the account. What the account did before it converted. Which activation step correlates with a second renewal. Which cohort of signups retained better after a pricing change. Those questions need behaviour, and behaviour is what Analytics records.
If one dashboard is unavoidable, join the two in BigQuery on your own customer key. That is what user_id is for, with two rules attached: never send anything that identifies a person, and never register User-ID as a custom dimension, because at high cardinality it collapses into an (other) row.
A short audit for this week
- Open one purchase event in DebugView and check whether
valueincludes VAT. If it does, fix the source, not the report. - Check whether
transaction_idis populated on every purchase, including the server side path. Look for an empty string rather than a missing parameter. - Confirm that a refund event exists and carries the original invoice number.
- List every way a cancellation reaches Analytics. If the answer is none, add the event before the next churn review.
- Write down which revenue questions the billing system answers and which ones GA4 answers, and put that sentence in the report deck.
None of this makes GA4 a subscription ledger, and it was never going to be one. What it does is stop two teams arguing about a number that was never measuring the same thing, and give the marketing side a defensible answer to the only question finance actually asks, which is where the next hundred accounts come from.
Do Your Revenue Numbers Agree?
North Digital reviews GA4 and GTM event models against the figures your finance team reports, so purchases, refunds and renewals land once and land clean. Every parameter is documented, including the ones that were left out on purpose.
Talk to North Digital