Respectlytics Respect lytics
Menu
Kotlin (Android) Trial → paid conversion Privacy-first

How to track trial-to-paid conversion in Kotlin (Android) without personal data

Trial conversion — the moment a free trial converts to a paying subscription — is the second-most lucrative event in subscription analytics, and one of the most PII-heavy by default. Respectlytics helps developers avoid collecting personal data in the first place: in Kotlin (Android), the conversion event is one named call with no transaction ID, no plan, no user identifier. Your billing vendor has the authoritative revenue truth; Respectlytics is your in-product engagement signal. Below: the Kotlin (Android) pattern, common pitfalls, and how to size the funnel.

Wire this into the same code path that handles trial-to-paid renewal — typically a webhook from your billing vendor, surfaced to the client through your own backend, OR a client-side StoreKit transaction listener. Avoid passing the transaction ID, the renewal amount, or the new plan — those live in your billing system.

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
// Server-side handler for Google Play Real-Time Developer Notifications:
fun onSubscriptionNotification(notification: SubscriptionNotification) {
    // SUBSCRIPTION_RENEWED firing on a sub that previously had a free trial = conversion.
    if (notification.notificationType == NotificationType.SUBSCRIPTION_RENEWED &&
        notification.previouslyHadFreeTrial) {
        respectlyticsClient.track("trial_conversion", platform = "android")
    }
}

Trial conversion is most reliably tracked server-side via Google Play's RTDN. The client-side BillingClient typically learns about background renewals only the next time the user opens the app — too late for an accurate conversion rate.

Privacy & implementation notes

If both your client and your backend listen to billing events, ensure exactly one of them fires the analytics event — usually the backend, because it sees the authoritative webhook even when the user has closed the app. Double-firing produces conversion rates above 100%, which always looks more dramatic than the bug actually is.

Treat trial conversion as two separate metrics with separate systems of record: the product engagement signal (Respectlytics, session-scoped) and the revenue signal (your billing system, per-user). Asking analytics to do both ends with neither being trustworthy.

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

Trial conversion eventFirebase AnalyticsMixpanelRespectlytics
Per-user revenue attributionYesYesOut of scope (use billing system)
Transaction ID storedYesYesRejected by API
Renewal amount / currency storedYesYesRejected by API
Plan ID storedYesYesUse distinct event_name per plan
Useful for product funnel?YesYesYes (session-grouped)
Useful for revenue forecasting?YesYesNo (use billing system)

Frequently asked questions

Where should this event fire — client or server?

If your client gets a verifiable in-app receipt (StoreKit, Billing Library), the client is fine. If you rely on RevenueCat / Stripe webhooks for the source of truth, fire from your backend after the webhook resolves — using your server-side Respectlytics SDK or REST call. Don't fire on both — you'll double-count.

What if the user converts via a desktop web flow, not in-app?

Fire from your backend when the conversion completes — tagged with the platform field set to whatever channel produced it (platform: web). The session ID for backend-fired events is generated server-side and rotates the same way.

How do we handle different plan tiers (basic, pro, team)?

Distinct event names per tier: trial_conversion_basic, trial_conversion_pro, etc. Keep tier count modest; bucket long tails as _other.

What about double-counting if the user converts and then refunds within 24h?

Don't try to retract the analytics event. Your billing system has the refund data. The product question is "how many trials converted at all?" — that's what trial_conversion measures. Refund analysis happens against the billing system, not the analytics pipeline.

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.