TL;DR: One trial to paid conversion rate cannot compare iOS, Android, and web trials until three things are fixed: who counts as a trial, which store states mean "converted", and when a cohort is mature. A normalization worksheet makes each decision explicit.
A lifecycle or subscription-ops lead usually discovers the problem in a review meeting: App Store Connect reports one trial conversion number, the Play Console reports another, and the web billing dashboard reports a third — for the same product and the same week. None of them is wrong. Each platform counts a different denominator, uses a different state vocabulary, and closes the books on a different schedule. To measure trial-to-paid conversion by billing platform, you do not need a better dashboard. You need three normalization decisions made once, on paper, before any rate is compared.
The decisions are: who counts as a trial start, which platform states map to "converted" and which only look like it, and how long a cohort must mature before its rate is reported. This is the same measurement discipline the business process automation ROI framework applies to any funnel — fix the units and the definitions before trusting the numbers.
Why one rate cannot describe three platforms
A single blended app trial conversion rate hides three structural differences.
Different denominators. Apple's own App Store Connect analytics define "Trial to Paid Conversion" as the percentage of completed trial offers that converted to a paid subscription — trials still running are excluded from the denominator (Apple App Store Connect analytics). Most internal dashboards divide by trials started. On the same data, a start-date denominator always produces a lower rate than a completed-trial denominator.
Different state vocabularies. A Stripe trial moves through trialing, incomplete, active, past_due, canceled, unpaid, or paused (Stripe subscription lifecycle). A Google Play subscription reports subscriptionState values such as SUBSCRIPTION_STATE_ACTIVE and SUBSCRIPTION_STATE_IN_GRACE_PERIOD, plus an offerPhase field that marks the free-trial phase (Google Play subscription lifecycle). Apple reports notifications like DID_FAIL_TO_RENEW with a GRACE_PERIOD subtype. None of these words mean the same thing across platforms.
Different observation windows. Trial length itself differs by platform: Apple offers fixed trial lengths from 3 days to 1 year, while Google Play allows any length between 3 days and 3 years (RevenueCat trial conversion analysis). Add grace periods and delayed payment recovery, and two cohorts that started on the same day reach a trustworthy rate weeks apart.
The fix is not averaging the three numbers. It is building one counting rule, one state map, and one maturity rule — then letting each platform keep its native reporting beside the normalized view. Whether the number in question is a SaaS trial conversion rate from web billing or a store trial in the same portfolio, the same three rules apply.
Step 1: Define trial eligibility and maturity
Count a trial the same way everywhere before comparing platforms: one person, one first trial, one cohort date. Three eligibility rules settle most disputes.
- First trial only. A subscriber who trials, cancels, and re-trials a year later still counts once. Cohort tools already work this way — RevenueDot's trial conversion methodology, for example, groups customers by first trial start date and counts each customer once (RevenueDot methodology).
- Split card-required from no-card trials. These are different populations, not one funnel. Recurly's 2022 merchant data put trial conversion at 39.0% when billing information was required up front versus 12.0% when it was not (Empact Partners compilation). Mixing them produces a rate that describes nobody.
- Stamp the eligibility moment per platform. On web billing, the subscription trial starts when the subscription object is created. On stores, it starts when the trial offer is purchased in-app. If your product also gates trials behind an activation event — say, completing onboarding — define that as a qualifier, not the start. The same judgment call behind choosing SaaS activation metrics that predict value applies here: the qualifier must be observable and identical across platforms.
Maturity is the second half. A trial cohort is immature while any member can still change buckets, and still-running trials belong in the denominator — RevenueDot keeps them there and flags recent cohorts as incomplete rather than letting them inflate the rate. Adapty likewise notes that a large share of eventual conversions land after the formal trial end (it cites Totango research around 50%), which is why snapshot rates undersell young cohorts (Adapty on trial conversion rates).
Set the maturity cutoff as trial end + payment-recovery window, not trial end. On Play, that window includes the grace period; on Stripe, the first charge resolves inside hours, but failed renewals churn through past_due. The discipline resembles judging ad campaigns — the same patience rule covered in how long to run Google Ads before judging performance: decide the window before looking at the result, or every early look becomes a bad decision.
Step 2: Map each platform's lifecycle states
Map every platform-native status to one internal vocabulary of six stages: trialing, pending payment, converted, cancelled (still active), expired, and billing recovery. The map is what turns three state machines into one comparable column.
| Normalized stage | App Store (App Store Server Notifications v2) | Google Play (subscriptionsv2) | Stripe / web billing | RevenueCat webhook |
|---|---|---|---|---|
| Trialing | SUBSCRIBED + INITIAL_BUY with trial offer |
SUBSCRIPTION_STATE_ACTIVE + offerPhase.freeTrial |
trialing |
INITIAL_PURCHASE (period_type=TRIAL) |
| Pending payment | DID_FAIL_TO_RENEW (no grace subtype) |
Renewal pending after expiry | incomplete (23-hour window) |
BILLING_ISSUE |
| Billing recovery | DID_FAIL_TO_RENEW + GRACE_PERIOD |
SUBSCRIPTION_STATE_IN_GRACE_PERIOD (autoRenewEnabled stays true) |
past_due during retries |
BILLING_ISSUE / CANCELLATION with cancel_reason=BILLING_ERROR |
| Cancelled (still active) | DID_CHANGE_RENEWAL_STATUS + AUTO_RENEW_DISABLED |
autoRenewEnabled=false, still entitled |
cancel_at_period_end set, still active/trialing |
CANCELLATION (auto-renew off) |
| Converted | DID_RENEW after trial (or offer conversion) |
SUBSCRIPTION_RENEWED into basePrice phase |
trialing → active on first payment |
RENEWAL / first paid INITIAL_PURCHASE |
| Expired | EXPIRED + VOLUNTARY / BILLING_RETRY subtype |
SUBSCRIPTION_STATE_EXPIRED |
canceled / unpaid / incomplete_expired |
EXPIRATION |
The v2 subtype mapping above follows the AppsOps App Store Server Notifications guide, the RevenueCat webhook event reference, the Google Play lifecycle doc, and the Stripe subscription docs. Three details in the map routinely corrupt reported rates:
- Platform dashboards silently shrink denominators. Apple's Active Plans metric excludes subscriptions in billing retry and grace period (Apple App Store Connect analytics). A native "active subscribers" tile can understate users who still hold entitlement during payment recovery.
- Store events have no cross-platform equivalent. RevenueCat's
SUBSCRIPTION_PAUSEDfires only on Google Play, andINVOICE_ISSUANCEonly on RevenueCat Billing — a normalized schema needs an explicit "platform-only" bucket instead of dropping those events. - A lapsed resubscribe is not a new conversion. RevenueCat fires
RENEWALboth when a subscription renews and when a lapsed user resubscribes (RevenueCat webhook event reference); win-back revenue must not re-enter the trial-conversion numerator.
If your stack already pipes store and web events through one billing layer, the identity work underneath the map — joining an app user to a billing subscriber — is the same plumbing described in web to app attribution for subscription apps. The map only works when the person behind app_user_id is actually one person.
Step 3: Separate cancellation from failed conversion
A cancelled trial and a failed conversion are different outcomes, and reporting them as one "did not convert" bucket hides the fixable half of the funnel. Cancellation is a decision: the user turned off auto-renew or asked to cancel while still entitled. Failed conversion is a mechanic: the trial ended and no successful first payment ever happened.
The platforms expose the difference precisely — if you read the right fields:
- App Store:
AUTO_RENEW_DISABLEDis the cancellation signal while access continues;EXPIREDwith subtypeVOLUNTARY,BILLING_RETRY,PRICE_INCREASE, orPRODUCT_NOT_FOR_SALEis the terminal outcome (AppsOps notification guide). A user can sit "cancelled" for days inside the trial and still convert if they re-enable — Apple even reportsAUTO_RENEW_ENABLEDas the reverse. - Google Play:
autoRenewEnabled=falsemarks the cancelled-still-active state, while grace period keepsautoRenewEnabled=trueand extendsexpiryTimedynamically — the user is still entitled, so neither cancelled nor failed (Google Play lifecycle). - Stripe / web: a failed first payment parks the subscription in
incompletefor up to 23 hours, then drops it toincomplete_expired— a terminal state where nothing was ever billed (Stripe subscription lifecycle). Separately, a trial ending with no payment method on file can leave the subscriptionpausedrather than converted or cancelled, andcustomer.subscription.trial_will_endfires a few days early so you can verify a payment method exists (Stripe subscription webhooks). - RevenueCat:
CANCELLATIONmeans auto-renew was turned off or a refund occurred — the subscription may still be active;EXPIRATIONis the separate event where access ends, andBILLING_ISSUEis explicitly not an expiry (RevenueCat webhook event reference).
The outcome taxonomy that falls out of this: converted, cancelled during trial (still deciding until expiry), failed — no payment, expired unpaid, billing recovery pending, and still running. Only the first goes in the numerator; the last two never belong in a finished cohort's denominator judgment at all.
The trial cohort comparison sheet
The worksheet that holds all of this together is one table per platform-cohort. Its columns force the three normalization decisions before any rate is computed:
| Block | Columns | Rule it enforces |
|---|---|---|
| Eligibility | cohort_id, platform, trial_start_date, card_required, trial_length_days, offer_type |
First trial only, deduped; card and no-card cohorts never share a row |
| State map | native_status, event_source, normalized_stage |
Every incoming event must land in one of the six stages or a named "platform-only" bucket |
| Observation | trial_end_date, maturity_cutoff (end + recovery window), still_running, incomplete_flag |
A cohort is reported only after maturity_cutoff; before that it is flagged, not compared |
| Outcome | converted, cancelled_still_active, failed_no_payment, expired_unpaid, recovery_pending |
Numerator is converted only; cancelled and failed stay separate lines |
Synthetic worked example. Same launch week, same product, 1,000 trial starts per platform, snapshot pulled 5 days after the longest trial ends — deliberately too early:
| Platform row (synthetic) | Starts | Completed trials | Converted | Still running / pending | Rate ÷ completed | Rate ÷ starts |
|---|---|---|---|---|---|---|
| iOS, 7-day trial | 1,000 | 940 | 282 | 60 trialing | 30.0% | 28.2% |
| Android, 14-day trial | 1,000 | 905 | 253 | 60 trialing + 35 grace | 28.0% | 25.3% |
| Web, 14-day no-card | 1,000 | 930 | 112 | 40 trialing + 30 incomplete/paused |
12.0% | 11.2% |
Read it honestly: the "completed" column reproduces something close to Apple's own metric (which divides by completed offers), while "starts" is the denominator most internal dashboards use. At this snapshot the iOS row looks 2.7× better than web — yet the web row still carries 70 unresolved members, and the Android row carries 35 grace-period members who are still entitled and might recover. The web cohort is also a different population (no card required), which alone can explain most of the gap — Recurly's 39.0% versus 12.0% card-required split shows how large that effect can be. Recompute after maturity_cutoff and only the converted column should move; if it does not, the window was set too short.
These numbers are invented to show the arithmetic, not a benchmark. Copy the column structure into a spreadsheet or BI model and fill it from your own store exports, Stripe reports, or billing-layer events.
Decide which number goes to the dashboard
Ship the normalized rate as the headline, keep platform-native rates as diagnostic columns, and never publish an immature cohort without its incomplete_flag. Concretely:
- Report mature cohorts only. Dashboard tiles show cohorts past
maturity_cutoff; younger cohorts display as "n of N still resolving" rather than a rate. - State the denominator on the chart. "Of completed trials" and "of started trials" are both defensible — pick one, write it on the tile, and keep it identical across platforms.
- Keep outcome buckets visible. A falling rate split into "more failed payments" versus "more cancellations" points at billing recovery or at product value — opposite fixes for opposite causes.
- Treat external benchmarks as orientation, not targets. RevenueCat's 2026 dataset across 115,000+ apps put median trial-to-paid near 32.6% on the App Store and 32.5% on Google Play — for store trials that bill automatically, on that report's own definitions (Empact Partners compilation). A trial to paid conversion rate benchmark built on a different denominator is a benchmark of a different metric: those medians answer "are we in a sane range," not "what should our number be."
- Choose the decision, then the metric. If the real question is trial design, a controlled experiment such as monthly-to-annual subscription conversion framing beats rate-watching; one randomized 337,724-user test found 7-day and 30-day trials within a point of each other (15.36% vs 14.67%, via the same compilation).
Where this breaks
Three boundaries deserve honest exits. Promotional and code-based trials skip parts of the billing event stream entirely — RevenueCat's compatibility matrix shows Promo columns missing INITIAL_PURCHASE and RENEWAL, so those trials need their own counting rule or exclusion. Refunds can reverse a recorded conversion after the fact, so mature cohorts still need a late-adjustment pass. And the same person can hold an App Store subscription trial and a web trial at once; without identity resolution across app_user_id, email, and Stripe customer, that person lands in two denominators.
If the sheet shows three platforms, six stages, and nobody owning the definition, the problem is governance, not instrumentation. An automation audit from That'sGonnaHelp can bound which events, states, and owners the normalized metric needs before the next reporting cycle.
FAQ
What is a good trial conversion rate? There is no single good number — it depends on denominator, card requirement, and platform. For orientation only: RevenueCat's 2026 data across 115,000+ apps reports median trial-to-paid near 32.6% on the App Store and 32.5% on Google Play for auto-billing store trials, while Recurly's 2022 merchants saw 39.0% with a card required versus 12.0% without (Empact Partners compilation). Compare only against benchmarks that share your trial model.
What is trial conversion rate? The percentage of trial starts — or, in Apple's analytics, completed trial offers — that convert to a paid subscription: converted trials divided by the chosen denominator (some teams track the same thing as the trial to purchase conversion rate). The free trial conversion rate formula is simple; the denominator choice is the entire dispute this article is about.
Is a cancelled trial the same as a failed conversion?
No. Cancellation is a user decision — auto-renew off while still entitled, and still reversible until expiry (AUTO_RENEW_ENABLED exists for a reason). Failed conversion is mechanical: the trial ended with no successful first payment, as in Stripe's incomplete_expired state. They need different fixes and different reporting lines.
How long should a trial cohort mature before reporting?
Until every member has resolved: trial end + the platform's payment-recovery window. That means covering Play grace periods, Stripe's past_due retry cycle, and late conversions — research cited by Adapty suggests around half of eventual conversions can land after the formal trial end.
Does a free trial count as a subscription?
Platform-dependent. Apple counts free trials inside its subscription-offer metrics; Stripe models a free trial as subscription status trialing; Play exposes the trial through offerPhase on an active subscription. For your own counting, a trial is a subscription in a pre-paid stage — which is exactly why the state map matters.
Answer clarity notes
- Dates: the 2026 benchmark medians and the 2022 Recurly figures reflect their linked sources and publication contexts; platform trial rules, notification subtypes, and subscription states change — check current Apple, Google, Stripe, and RevenueCat docs before implementing.
- Evidence: platform state and event details come from the linked Apple, Google, Stripe, RevenueCat, and AppsOps documentation; benchmark statistics are secondary compilations used for orientation only, on their original definitions.
- Synthetic example: the 1,000-start cohort table is invented to make the denominator and maturity arithmetic reproducible; it is not a client result, a measured benchmark, or a prediction of your rates.
- Scope: written for lifecycle, product, and subscription-ops leads at mobile or consumer subscription businesses selling via App Store, Google Play, and web billing (Stripe or a billing layer such as RevenueCat). US-market B2B context; not legal, tax, or platform-policy advice.
- Do not infer: observation-window lengths, outcome taxonomies, and external medians are planning guidance on stated assumptions, not guarantees.
Sources
- Subscriptions — App Store Connect Analytics, Apple
- Subscriptions lifecycle — Google Play Billing
- Event Types and Fields — RevenueCat Webhooks
- How subscriptions work — Stripe
- Configure trial offers on subscriptions — Stripe
- Using webhooks with subscriptions — Stripe
- App Store Server Notifications v2 lifecycle guide — AppsOps
- Free Trial Conversion Statistics — Empact Partners
- Trial Conversion Rate methodology — RevenueDot
- Trial conversion rates for in-app subscriptions — Adapty
- App trial conversion rate insights — RevenueCat