11.10 — Analytics, RevenueCat & Growth

Opening scenario

You ask your CEO at standup: “What’s our trial→paid conversion rate?” Silence. Open App Store Connect: aggregate weekly downloads and revenue. Open RevenueCat (if installed): cohort retention, MRR, trial conversion. Open Mixpanel: in-app event funnels. Open Appsflyer: install attribution. Each tool answers part of the question; the engineering effort of stitching them together is what separates “we have data” from “we have answers.” This chapter is the analytics stack that turns shipping into a feedback loop.

Context taxonomy

ToolLayerWhat it answers
App Store Connect AnalyticsApp Store funnelImpressions → product views → downloads → IAP revenue
RevenueCat / AdaptySubscription lifecycleMRR, ARR, churn, cohort LTV, paywall A/B
Mixpanel / AmplitudeIn-app behaviorEvent funnels, retention curves, segment analysis
Appsflyer / Adjust / Branch / KochavaInstall attribution (MMP)Which campaign drove which install
Apple Search Ads (via AdServices)Apple’s ad network attributionSearch Ads campaign ROI
Sentry / Firebase CrashlyticsCrash & error trackingStability metrics — leading indicator of churn
Statsig / LaunchDarkly / OptimizelyFeature flags + experimentsCausal impact of feature changes
PosthogSelf-hosted product analyticsOpen-source alternative to Mixpanel
Google Analytics for FirebaseCross-platform behaviorDefault Firebase stack analytics
ASO tools — App Annie/data.ai, Sensor Tower, MobileActionCompetitive intelligenceCategory ranks, keyword positions, competitor downloads

Concept → Why → How → Code

Concept. Analytics for paid apps is a stack: acquisition (MMP + Apple Search Ads + SKAdNetwork), in-app behavior (Mixpanel/Amplitude/Posthog), revenue (RevenueCat/Adapty + App Store Connect), and stability (Sentry/Crashlytics). Each layer answers different questions; you wire them via a single user identifier (your own userID) that flows through every system.

Why. Without the stack, you ship blind. With it, every release is measured against a baseline; every A/B test produces a verdict; every churn cohort can be reverse-engineered to root cause. Compound that over a year and you out-iterate competitors who guess.

Layer 1 — App Store Connect Analytics

Built-in. Read via App Store Connect web UI or REST API.

# Sales report via App Store Connect API
TOKEN=$(python3 scripts/asc_jwt.py)
DATE=2026-11-22

curl -H "Authorization: Bearer $TOKEN" \
  -H "Accept: application/a-gzip" \
  "https://api.appstoreconnect.apple.com/v1/salesReports?\
filter[frequency]=DAILY&\
filter[reportType]=SALES&\
filter[reportSubType]=SUMMARY&\
filter[vendorNumber]=12345678&\
filter[reportDate]=$DATE&\
filter[version]=1_0" \
  --output sales-$DATE.tsv.gz

The funnel App Store Connect exposes:

Impressions          ← App Store search/browse appearances
   ↓ ~3% (ASO-dependent)
Product Page Views   ← User tapped your listing
   ↓ ~30% (screenshots, video, description quality)
Downloads (App Units)
   ↓ varies wildly
Sessions             ← Active usage
   ↓ retention curves
IAP Revenue          ← After Apple cut

The conversion from Impressions → Product Page Views is the highest-leverage metric most teams ignore. Improving ASO (icon, screenshots, video, keywords) moves this number 2–10×.

Layer 2 — RevenueCat for subscription analytics

import RevenueCat

@main
struct MyApp: App {
    init() {
        Purchases.logLevel = .info
        Purchases.configure(withAPIKey: "appl_xxxx", appUserID: currentUserID)
    }
    var body: some Scene { WindowGroup { ContentView() } }
}

// Read entitlements anywhere
extension Purchases {
    var isPro: Bool {
        get async {
            let info = try? await customerInfo()
            return info?.entitlements["pro"]?.isActive == true
        }
    }
}

// Tag custom subscriber attributes for cohort slicing
Purchases.shared.attribution.setMixpanelDistinctID("user_123")
Purchases.shared.attribution.setAdjustID("adjust_id_xyz")
Purchases.shared.attribution.setCampaign("brand_search_2026")

RevenueCat dashboard gives you out-of-the-box:

  • MRR / ARR
  • Trial start, trial conversion, churn
  • Cohort retention curves (e.g., “users who started a trial in Jan 2026: 34% still subscribed at month 6”)
  • LTV by paywall variant, country, attribution source
  • Webhook events for downstream systems

A typical RevenueCat→Mixpanel wiring:

# Webhook receiver — RevenueCat → Mixpanel
@app.post("/webhooks/revenuecat")
async def revenuecat_webhook(req: Request):
    body = await req.json()
    evt  = body["event"]
    user_id = evt["app_user_id"]

    mp_event = {
        "INITIAL_PURCHASE":          "subscription_started",
        "RENEWAL":                   "subscription_renewed",
        "CANCELLATION":              "subscription_cancelled",
        "BILLING_ISSUE":             "billing_issue",
        "SUBSCRIPTION_PAUSED":       "subscription_paused",
        "UNCANCELLATION":            "subscription_uncancelled",
        "NON_RENEWING_PURCHASE":     "one_time_purchase",
    }.get(evt["type"])

    if mp_event:
        mixpanel.track(
            distinct_id=user_id,
            event_name=mp_event,
            properties={
                "product_id":   evt["product_id"],
                "price":        evt["price"],
                "currency":     evt["currency"],
                "is_trial":     evt["period_type"] == "TRIAL",
                "store":        evt["store"],
            }
        )
    return {"ok": True}

Layer 3 — Mixpanel / Amplitude in-app events

import Mixpanel

// In App init
Mixpanel.initialize(token: "your_token", trackAutomaticEvents: false)
Mixpanel.mainInstance().identify(distinctId: currentUserID)

// Track custom events
Mixpanel.mainInstance().track(event: "Lesson Completed", properties: [
    "lesson_id": lesson.id,
    "duration_seconds": Int(duration),
    "completion_score": score,
])

// User profile properties for cohort building
Mixpanel.mainInstance().people.set(properties: [
    "$name":          user.name,
    "subscription":   user.tier,
    "signup_date":    user.signupDate,
])

Build funnels in the Mixpanel UI:

App Open
  → Lesson Started      (70% drop)
    → Lesson Completed  (40% drop)
      → Paywall Shown   (5% drop)
        → Trial Started (35% drop)
          → Paid        (3 months later)

Each step’s drop-off is a hypothesis to test.

Layer 4 — MMP attribution (Appsflyer / Adjust)

import AppsFlyerLib

// AppDelegate
AppsFlyerLib.shared().appsFlyerDevKey = "your_dev_key"
AppsFlyerLib.shared().appleAppID      = "1234567890"
AppsFlyerLib.shared().customerUserID  = currentUserID

func application(_ app: UIApplication, didFinishLaunchingWithOptions: ...) -> Bool {
    AppsFlyerLib.shared().start()
    return true
}

// Track purchase events (sends to MMP for ROAS attribution)
AppsFlyerLib.shared().logEvent(
    AFEventPurchase,
    withValues: [
        AFEventParamPrice: 49.99,
        AFEventParamCurrency: "USD",
        AFEventParamContentId: "annual_subscription",
    ]
)

MMP’s job: tell you which Facebook/TikTok/Google campaign drove each install, and which installs eventually generated revenue. ROAS (Return on Ad Spend) reports tell you which campaigns to scale.

Layer 5 — ASO (App Store Optimization)

Tools: Sensor Tower, App Annie / data.ai, MobileAction, Asodesk.

Workflow:

  1. Identify 30–50 candidate keywords with relevant search volume in your category
  2. Find your current rank per keyword (lower = better; top 10 typically required for meaningful traffic)
  3. Update your app’s title, subtitle, and keyword field (Apple gives you 100 char hidden keyword field)
  4. Iterate screenshots/video → measure conversion lift in App Store Connect Analytics
  5. Repeat quarterly

Example:

Before:
  Title: "Acme Notes" (no keyword juice)
  Subtitle: "A great notes app"
  Keyword field: "notes,writing,memo"

After:
  Title: "Acme Notes — Markdown Editor"
  Subtitle: "Sync notes, organize ideas, beautifully"
  Keyword field: "markdown,obsidian,bear,journal,zettelkasten,outline,..."

Result: rank for "markdown editor" goes from #47 to #11; impressions × 8.

Growth experimentation loop

1. Hypothesize ← e.g., "longer trial → higher trial-to-paid conversion"
2. Design     ← 7-day vs 14-day trial, randomized via paywall_variant
3. Instrument ← Mixpanel events with variant tag
4. Ship       ← RevenueCat Offerings / Custom Product Pages to expose variants
5. Wait       ← need 2× max(trial period) for clean data
6. Analyze    ← cohort LTV per variant in RevenueCat
7. Decide     ← winner becomes default; loser sunsetted
8. Repeat

A typical pattern: one growth experiment per 2 weeks. Over a year that’s 24 tested hypotheses; even at 30% win rate, ~7 wins compounding multiplicatively can double overall LTV.

In the wild

  • Duolingo publicly attributes its growth to constant A/B testing — their growth team runs ~50 experiments/quarter.
  • Spotify runs hundreds of paywall A/Bs per quarter via internal tooling that resembles RevenueCat Offerings on steroids.
  • Headspace publicly cited RevenueCat as the tool that “unlocked their experimentation velocity.”
  • Calm runs continuous paywall A/Bs — observed shipping ~3 different paywalls in a single week across cold-install cohorts.
  • Pokemon Go (Niantic) uses Adjust as MMP, Mixpanel for in-app, RevenueCat-like internal tooling.

Common misconceptions

  1. “App Store Connect Analytics is enough.” It’s enough to know revenue and downloads. It can’t tell you why users churned, what features they used, or which paywall version converted best.
  2. “Mixpanel and Amplitude are basically the same.” Mixpanel is more event-funnel-oriented; Amplitude leans behavioral cohorting. Both work; pick one and commit.
  3. “You need every tool from day one.” Day-one stack: Crashlytics + RevenueCat + a basic in-app event tool. Add MMP when paid acquisition begins. Add ASO tools when you’re optimizing organic.
  4. “RevenueCat is just a paywall SDK.” It’s also a webhook router, an A/B test platform, an analytics dashboard, and a cross-platform abstraction. Worth using even if you don’t need the paywall SDK.
  5. “Privacy laws killed analytics.” They killed cross-app tracking without consent. First-party in-app behavior tracking (your own events) is unrestricted as long as you disclose in your privacy policy and Privacy Manifest.

Seasoned engineer’s take

TIP. Wire one user identifier (your internal userID) into every analytics tool from day one. Cross-system join later is impossible if events are tagged with different IDs.

WARNING. Many ad networks and MMPs offer “free for early stage.” That free tier is data-rate-limited; production volumes hit caps and your data goes silent without warning. Read the contract.

The mindset that separates teams that compound from teams that ship features: data is a product surface, not a side-channel. Every release should produce measurable behavioral output; every metric should map to a decision; every decision should be reviewed against the metric’s movement. That feedback loop is the growth function.

Interview corner

Junior“What’s RevenueCat?” A subscription analytics + paywall SDK that abstracts StoreKit (iOS) and Google Play Billing (Android) into a single API, with built-in webhooks, cohort analysis, and A/B testing infrastructure.

Mid“How would you measure if a paywall change improved conversion?” RevenueCat Offerings or Custom Product Pages to expose variants A and B to cold-install cohorts. Tag each user’s variant in your analytics tool. Wait ≥ 2× trial period for clean data. Compare trial→paid conversion + LTV at 30/60/90 days between cohorts.

Senior“Design an analytics stack for a $1M ARR subscription app planning to grow to $10M.” Crashlytics for stability (leading churn indicator). RevenueCat for subscription truth + paywall A/B. Mixpanel or Amplitude for in-app event funnels. Appsflyer for paid acquisition attribution. App Store Connect API ingested daily for organic metrics. Sentry for non-fatal errors. All systems tagged with a single internal userID. Weekly experimentation cadence with PRD → instrumentation → ship → measure → decide loop. Dashboard in Metabase/Looker pulling from a unified BigQuery/Snowflake warehouse fed by RevenueCat exports + Mixpanel exports.

Red flag“We rely entirely on App Store Connect Analytics.” You’re flying blind on retention, behavior, and attribution. Add at minimum RevenueCat + an in-app event tool before your next pricing decision.

Lab preview

Lab 11.1 ships a real RevenueCat-integrated paywall — the first piece of the analytics stack. Lab 11.2 ships pricing automation — the operational backbone for any data-driven pricing decisions.


Next: Lab 11.1 — Subscription Paywall