Respectlytics Respect lytics
Menu
Kotlin (Android) Paywall conversion Privacy-first

How to track paywall conversion in Kotlin (Android) without personal data

Paywall conversion is the single highest-stakes event in most subscription apps — and the one most likely to leak personal data into your analytics pipeline. Respectlytics helps developers avoid collecting personal data in the first place: in Kotlin (Android), you emit a single named event when a purchase succeeds, with no price, no product ID, no user identifier. The session ID rotates every two hours, so a purchase becomes "a purchase happened in this session" — sufficient for funnel analysis, insufficient for cross-app re-targeting. Below: a complete Kotlin (Android) integration, the privacy gotchas specific to your platform, and how the resulting Respectlytics events compare to Firebase Analytics or Mixpanel.

The tracking call is one line. Place it where your purchase observer sees a successful transaction — the same callback you'd already use to grant entitlement to the user. Avoid the temptation to pass price, currency, product SKU, or user ID; Respectlytics's API rejects extra fields with a 400 Bad Request, so this fails fast in development if a teammate adds a field by reflex.

Install the Kotlin (Android) SDK

kotlin Respectlytics
// build.gradle.kts (app module)
dependencies {
    implementation("com.respectlytics:respectlytics-kotlin:3.0.0")
}

Pure Kotlin coroutines implementation. No Java dependencies, no Google Play Services dependencies. ~300KB DEX overhead — compare to roughly 3.8MB for Firebase Analytics (a measurable cold-start improvement on lower-end devices).

Initialize Respectlytics in Kotlin (Android)

kotlin Respectlytics
import com.respectlytics.android.Respectlytics

class MyApplication : Application() {
    override fun onCreate() {
        super.onCreate()
        Respectlytics.configure(this, appKey = "<YOUR_APP_KEY>")
    }
}

Initialize once in Application.onCreate. No additional permissions in the manifest — INTERNET is sufficient. The SDK does not request AD_ID, does not query AdvertisingIdClient, and does not declare ACCESS_NETWORK_STATE.

Track the event in Kotlin (Android)

kotlin Respectlytics
import com.android.billingclient.api.*
import com.respectlytics.android.Respectlytics

class PurchaseHandler : PurchasesUpdatedListener {
    override fun onPurchasesUpdated(billingResult: BillingResult, purchases: List<Purchase>?) {
        if (billingResult.responseCode == BillingClient.BillingResponseCode.OK && purchases != null) {
            for (purchase in purchases) {
                if (purchase.purchaseState == Purchase.PurchaseState.PURCHASED) {
                    // Don't pass price, products list, or order ID. Session-scoped is enough.
                    Respectlytics.track("paywall_purchase")
                }
            }
        }
    }
}

Use this in conjunction with your BillingClient setup. The same pattern works with the Google Play Billing Library v6+ — the relevant state is PURCHASED, regardless of acknowledgement state.

Privacy & implementation notes

Most analytics SDKs default to tying purchase events to a persistent user_id or device-level identifier. Respectlytics's session_id rotates every two hours, so a paywall purchase becomes "a purchase happened in this session" — sufficient for funnel analysis, insufficient for resale or cross-app re-targeting. This is the data-minimization trade-off in one line.

The natural aggregation bucket is (country, platform, day). "78% of paywall views convert in DE on iOS today" is a useful comparison even without per-user identity. Most paywall A/B-test decisions are made on differences this granular — going down to per-user is operationally expensive and rarely changes the conclusion.

Many teams discover the com.google.android.gms.permission.AD_ID permission in their merged manifest only after Google Play flags them — usually because a transitive dependency dragged it in. Respectlytics's Kotlin SDK has no Google Play Services dependency at all, so it cannot contribute to that merge.

The SDK is implemented as pure Kotlin coroutines with no Java sources, no RxJava, and no platform channels. Events are queued in a Channel<Event> buffered to a small ring (RAM-only), drained by a coroutine that flushes every 30 seconds or on backgrounding. There is no SharedPreferences usage.

How this compares to other analytics SDKs

What gets sent on a paywall purchaseFirebase Analytics (default)Mixpanel (default)Respectlytics
IDFA / AAIDYes (with user consent)OptionalNever
Persistent user IDapp_instance_iddistinct_idNever
Purchase amount / currencyRecommendedRecommendedRejected by API
Product / SKU identifierRecommendedRecommendedRejected by API
Transaction IDRecommendedRecommendedRejected by API
IP address storedYesYes (90-day default)Used transiently for country, then discarded
What you can computePer-user LTV, cohort revenuePer-user LTV, cohort revenueSession-grouped funnel, country-level conversion rate

Frequently asked questions

How do we attribute revenue without a user ID?

You don't, at the user level. You attribute by session, country, and day. For most product decisions — "is the new paywall design converting more sessions in DE on iOS?" — that's enough. If you need per-user LTV for investor reporting, that lives in your billing system (Stripe, RevenueCat, App Store Connect) — not in your product analytics.

Can we still A/B-test paywall variants?

Yes. Emit different event_name values for each variant — e.g. paywall_purchase_a, paywall_purchase_b. Aggregation buckets them automatically. No randomized user assignment is stored — variant assignment lives in the client until the event fires.

What about refunds and chargebacks?

Out of scope for product analytics. Your billing system already has authoritative refund data, with the user IDs and amounts you legitimately need to act on. Mixing those into your conversion funnel produces double-counted noise — keep them separate.

Will the App Store reject my app for not collecting purchase metadata?

No. Apple's review guidelines do not require you to collect product or price metadata. Your Receipt and transactionIdentifier already exist on-device and on Apple's servers — duplicating them into your analytics pipeline is a choice, not a requirement.

Related guides

Track what matters. Collect nothing you don't.

Five-field event schema, RAM-only event queue, no IDFA, no AAID, no persistent user IDs. Helps developers avoid collecting personal data in the first place.