RevenueHog

// guides

How to track MRR for an iOS app

App Store Connect will tell you yesterday's sales, but not your monthly recurring revenue. Here's how to compute a defensible MRR from Apple's data (annual-plan normalization, trials, grace periods, proceeds vs price, currency) and the architecture to keep it honest.

last updated 2026-07-11 · by revenuehog

Why App Store Connect has no MRR number

App Store Connect reports what Apple needs to pay you: units, sales, proceeds, by day and territory. MRR is a derived, opinionated metric. It requires deciding that a $39.99/yr subscriber is "worth" $3.33/mo, that a free trial is worth $0, and that someone whose card failed yesterday still counts. Apple doesn't make those calls for you, so neither App Store Connect nor its App Analytics shows a normalized MRR series. Every iOS MRR number you've ever seen was computed by somebody, from the data below.

Defining MRR for App Store subscriptions

The definition that survives contact with Apple's data has three rules:

  • Normalize every billing period to months.Each subscriber contributes their per-period price divided by the period's length in months. Annual → ÷12, monthly → ÷1, weekly → ÷~0.23.
  • Decide who counts as paying. Active subscriptions, paid intro offers and promotional offers count. Grace-period and billing-retry subscribers should too: they still have entitlement and usually recover; excluding them makes MRR oscillate with card failures. Free trials never count: they pay $0. Track them as their own metric.
  • Intro offers at the price actually paid. A $0.99-first-month intro contributes $0.99, not the eventual $4.99. MRR states the present, not the forecast.
normalize.ts · the core computation
// The core of every App Store MRR number: normalize each active
// subscription's price to a monthly figure, then sum.
type ActiveSubRow = {
  pricePerPeriod: number; // what the subscriber pays per billing period
  periodMonths: number;   // 1 monthly, 12 annual, ~0.23 weekly (7 / 30.44)
  quantity: number;       // subscribers on this exact (product, price, state)
};

function monthlyRecurring(row: ActiveSubRow): number {
  if (row.periodMonths <= 0) return 0; // parse bug guard, not a real plan
  return (row.pricePerPeriod / row.periodMonths) * row.quantity;
}

const mrr = rows
  .filter((r) => isPayingState(r)) // active, paid intro, grace, billing retry
  .reduce((sum, r) => sum + monthlyRecurring(r), 0);

// $39.99/yr → 39.99 / 12 ≈ $3.33/mo per subscriber
// $1.99/wk  → 1.99 / 0.23 ≈ $8.65/mo per subscriber

The two data sources, and what each can't tell you

Daily SUBSCRIPTION reportServer Notifications V2
What it isA once-daily snapshot of active subscriptions per product, price, state and country (App Store Connect API)A signed HTTPS POST per lifecycle event (subscribe, renew, cancel, expire, refund) as it happens
FreshnessLags about a dayReal time (once wired)
HistoryAbout a year of daily reports back (Apple's limit)From the moment you set the URL (plus a 180-day replay API)
Best forMRR and subscriber levels: it's the authoritative countThe live feed, per-customer history, and signals reports omit (like auto-renew being turned off)
Blind spotsNo auto-renew status: a cancelled-but-unexpired trial looks identical to a live one. No per-transaction identity.No pre-wiring history; events, not levels (you'd have to replay state to derive MRR from events alone)

The pragmatic architecture uses both: levels from the report, moments from the notifications.Compute MRR from the daily snapshot; use the event stream for the feed and for corrections the report can't see. Trying to derive MRR purely from events means re-implementing Apple's subscription state machine. Doable, and a bug farm.

Levels vs flows (the classic dashboard bug)

The SUBSCRIPTION report doesn't arrive every day, and "no report" does not mean "zero subscribers". Subscriber counts and MRR are levels: on a gap day, carry the last observation forward. New subscriptions and revenue are flows: a missing day really is zero. Get this backwards and your MRR chart shows heart-attack cliffs on Apple's report-processing hiccups.

series.ts · gap-filling levels
// Apple's SUBSCRIPTION report is a daily *snapshot* of levels, and some
// days simply have no report. A missing day doesn't mean zero subscribers.
// Carry the last observed value forward (LOCF) when building the series.
function fillLocf(
  axis: string[], // every day on the chart, "YYYY-MM-DD"
  byDay: Map<string, number>, // days that actually reported
): { date: string; value: number }[] {
  let last = 0;
  return axis.map((date) => {
    if (byDay.has(date)) last = byDay.get(date)!;
    return { date, value: last };
  });
}
// The opposite rule applies to flows (new subscriptions/day, revenue/day):
// a missing day there really is zero. Confusing levels with flows is
// the classic MRR-dashboard bug.

Two related habits keep the numbers defensible: anchor "current MRR" to the most recent day that actually has data (and label it "as of" that day, since reports lag), and compute period-over-period deltas against the gap-filled value 30 days before the anchor, with no delta shown when the prior value is zero, because "+∞%" helps nobody.

Proceeds vs customer price

Apple takes 30%, or 15% if you're enrolled in the App Store Small Business Program. That gives every subscription two MRR figures: gross (customer price) and net(your proceeds). One note from production: Apple's daily reports already state Developer Proceeds at your actual rate — 85% if you're in the Small Business Program — so report-derived net MRR needs no adjustment. The only time the share matters is when you derive proceeds from a customer price yourself (a live per-event estimate), where you apply your own rate. The proceeds-math guide covers the distinction (and its caveats) in full.

Currency

Apple reports revenue per territory in dozens of local currencies. Convert each day's rows at that day's rate (the ECB publishes free daily reference rates) rather than one frozen rate, and flag anything you couldn't convert with a published rate as an estimate. Whatever you build, treat Apple's payout reports as the source of truth for exact amounts; a telemetry MRR that admits it's ±ECB-rate accuracy is honest, one that claims payout precision is lying.

The DIY architecture, sketched

  • Ingest: an App Store Connect API key (.p8, Issuer ID, Key ID) pulling the daily SALES and SUBSCRIPTION reports (gzipped TSV) into per-day tables, backfilling the ~365 days Apple allows.
  • Live events: an HTTPS endpoint receiving Server Notifications V2, verifying each JWS against Apple's root, deduping on notificationUUID.
  • Compute: the normalization above, run over the latest snapshot day; LOCF-filled series for charts; flows summed per window.
  • Convert: daily FX rates keyed by (date, currency), applied at read or ingest time.
  • Reconcile:monthly, against Apple's payout reports. Drift here is how you find bugs in all of the above.

It's a real but bounded project: a few weeks to solid, most of it in the edge cases this guide flagged. Paying states, gap days, Small Business proceeds, currency.

Or skip the build

RevenueHog computes exactly this: the same normalization, paying states, LOCF series, ECB conversion and Small Business adjustment, documented formula-by-formula on the metrics methodology page (no black boxes: you can check our math against this guide).

FAQ

Does App Store Connect show MRR?
No. App Store Connect's dashboards and App Analytics show sales, proceeds, units and active-subscription counts, but no normalized monthly-recurring-revenue number: annual plans aren't spread monthly, trials aren't separated from paying subscribers, and there's no single MRR series. You have to compute it from the reports (or use a tool that does).
Do free trials count toward MRR?
No. A trial pays $0/month, so it contributes $0 to MRR. Count trials as their own metric. Subscriptions in a billing grace period or billing retry shouldkeep counting: they're paying subscribers with a card hiccup, and most recover. Dropping them makes MRR flap.
How do annual subscriptions convert to MRR?
Divide by 12: a $39.99/yr subscriber contributes about $3.33/mo. Same principle for every period: divide the per-period price by the period's length in months (a weekly plan divides by roughly 0.23).
Should MRR use customer price or my proceeds?
Track both. Gross MRR (customer price) is comparable across tools and time; net MRR (proceeds after Apple's 30%, or 15% in the Small Business Program) is what you actually receive. Just label which one a chart shows; mixing them silently is how numbers stop being trusted.
Is MRR the same as monthly revenue?
No. MRR is a level: the recurring run rate right now. Monthly revenue is a flow: cash the month brought, including annual prepays and one-time purchases that MRR by definition spreads or ignores. A $39.99 annual purchase adds $39.99 to this month's revenue but only ~$3.33 to MRR. You want both numbers, separately.

sources

Apple: App Store Connect API, Sales and Trends reports (SALES/SUBSCRIPTION report types, daily granularity limits) — developer.apple.com/documentation/appstoreconnectapi.

Apple: App Store Small Business Program (15% commission) — developer.apple.com/app-store/small-business-program.

European Central Bank: euro foreign exchange reference rates — ecb.europa.eu.

Checked 2026-07-11.

// related