Revenue and subscriptions

Designing a Paywall → Purchase → Success Subscription Funnel

Updated 2026-09-144 min read

Short answer

Log the paywall being shown (once per display, with where it was shown), the purchase being started (when the store purchase sheet is requested), the purchase being confirmed (once per transaction, after verification), and the ways it ends without success — cancelled, failed or pending. Read them as a closed funnel of users, broken down by placement, and check confirmed purchases against the store.

Many subscription apps log a paywall view and a purchase, divide one by the other, and get a number nobody can act on. It does not say whether people saw the price and left, opened the store sheet and cancelled, or paid and hit an error. Each of those needs a different fix, and each needs its own event.

The events

EventLog it whenParameters worth adding
paywall_viewThe paywall is actually displayed — once per displayplacement (onboarding, settings, feature gate), paywall_id or variant
purchase_startThe user taps a product and the store purchase sheet is requestedproduct_id, placement
purchase_successThe transaction is verified and access is granted — once per transactionproduct_id, placement, is_trial
purchase_cancelThe store reports that the user cancelledproduct_id, placement
purchase_failThe store or your verification returns an errorproduct_id, error_code
purchase_pendingThe purchase waits for approval, such as Ask to Buy or a pending payment on Google Playproduct_id

Note: Firebase also collects in_app_purchase for App Store and Google Play purchases, and with StoreKit 2 you log it through Analytics.logTransaction(). Name your own event something other than purchase — purchase_success, for example — so GA4 does not count the same sale twice in its purchase revenue.

Where each event goes wrong

  • paywall_view logged on every render, or every time the screen reappears, inflates views and shrinks conversion
  • paywall_view logged for people who already subscribe measures a screen they cannot buy from
  • purchase_start logged when the paywall opens, rather than on a tap, makes the first step a copy of the view
  • purchase_success logged whenever the app finds an active subscription — on every launch, or after a restore — counts existing subscribers as new sales
  • Cancellation treated as an error hides normal hesitation among real failures

Reading the funnel

Build a closed funnel exploration in GA4 with paywall_view → purchase_start → purchase_success, counting users, and break it down by placement. Each step answers a different question.

StepIf it is low, look at
paywall_view → purchase_startPrice, trial offer, plan layout, and whether the paywall appears before people have seen any value
purchase_start → purchase_successCancellations in the store sheet, payment failures, pending approvals and verification errors
purchase_success by placementWhich moment in the app makes people ready to pay
Illustrative: similar conversion, different problems
                        Onboarding   Feature gate
paywall_view (users)         5,000            800
purchase_start                 400            120
purchase_success               300             60

View → success                6.0%           7.5%
Start → success                75%            50%

The feature gate converts better overall, yet half of the people who try to buy there do not finish. That is a checkout problem on that path — look at purchase_cancel and purchase_fail for that placement — and no amount of price testing on the paywall would have revealed it.

Checking it against the store

Confirmed purchases in your funnel should follow the store's own records. Compare purchase_success users on the iOS stream with new subscriptions in App Store Connect over the same days, and Android with Play Console. A steady gap means an event is missing or duplicated; a sudden gap usually means a release changed where it is logged.

Careful: Free trials turn into paid subscriptions days later, usually while the app is closed. The funnel measures trial starts; paid conversion comes from App Store Connect and Play Console, or from app_store_subscription_convert where Firebase collects it.

Before you ship it

  • Register placement, product_id and paywall_id as custom dimensions — event parameters do not appear in reports or explorations until you do, and registration does not fill in earlier data
  • Mark purchase_success as a key event
  • On one device in DebugView, walk through view, start, cancel, failure and success, and confirm each event arrives exactly once
  • Use sandbox accounts on iOS and license testers on Google Play for the walkthrough; GA4 ignores sandbox purchases for in_app_purchase, so the tests do not pollute revenue

How this looks in Appnaly

In Appnaly you can mark a start event and a completion event as the core journey of an app — purchase_start and purchase_success fit well. Appnaly then asks GA4's funnel report for the people who did both, in order, compares the rate with the previous completed week, and stays quiet until there are enough people for a change to mean something.

Free tools

Related reading