Respectlytics Respect lytics
Menu
Swift (iOS) Subscription renewal Privacy-first

How to track subscription renewals in Swift (iOS) without personal data

Subscription renewal is, by volume, the noisiest subscription event — it fires every billing cycle, often silently in the background. Most teams instrument it once and then discover it's dominating their analytics quota with metadata they never query. Respectlytics helps developers avoid collecting personal data in the first place: in Swift (iOS), renewal is one named event per billing cycle, with no payload. Revenue belongs in your billing system; product retention belongs in Respectlytics. Below: where to fire, what to leave out, and how to read retention without per-user joins.

Renewal events ideally come from a server-side webhook (App Store Server Notifications, Google Play RTDN, RevenueCat webhooks). Client-side StoreKit listeners can also fire on renewal but only when the user opens the app — your backend webhook is more reliable. Use one path or the other, not both.

Install the Swift (iOS) SDK

swift Respectlytics
// Package.swift
dependencies: [
    .package(url: "https://github.com/respectlytics/respectlytics-swift.git", from: "3.0.0")
]
// Or via Xcode → File → Add Packages → paste the URL above.

The SDK ships only via Swift Package Manager. CocoaPods and Carthage are not published — fewer integration paths means fewer surfaces to keep audited.

Initialize Respectlytics in Swift (iOS)

swift Respectlytics
import Respectlytics

@main
struct MyApp: App {
    init() {
        Respectlytics.configure(appKey: "<YOUR_APP_KEY>")
    }
    var body: some Scene { WindowGroup { ContentView() } }
}

Call configure once at app launch — typically in your App struct's init. No Info.plist keys are required: the SDK does not call ATTrackingManager and does not request the IDFA, so NSUserTrackingUsageDescription should NOT be added.

Track the event in Swift (iOS)

swift Respectlytics
import Respectlytics
import StoreKit

// Client-side observation of renewals (only fires when user opens the app):
Task {
    for await result in Transaction.updates {
        guard case .verified(let transaction) = result else { continue }
        // originalID != id means it's a renewal of a prior transaction.
        if transaction.revocationDate == nil &&
           transaction.originalID != transaction.id {
            Respectlytics.track("subscription_renewed")
            await transaction.finish()
        }
    }
}

Server-side firing from App Store Server Notifications V2's DID_RENEW is the more reliable path — it captures background renewals the client can't see.

Privacy & implementation notes

Background renewals don't open the app. If you instrument renewal only in your client-side StoreKit listener, you'll miss every renewal where the user doesn't happen to open the app within a few days. Server-side from a billing webhook is the reliable path — fire it via Respectlytics's REST API.

Use Respectlytics for renewal rate by country / platform / plan, and your billing system for MRR. The rate is what tells you whether a product change is moving retention; the dollar number is what your finance team wants. They're the same conceptually but operationally separate.

Apple rejected approximately 3% of apps in 2024 for incorrectly omitting NSUserTrackingUsageDescription when ATT was required by the SDKs they shipped. Respectlytics doesn't trigger ATT. The corollary is also true: do not add the key on Respectlytics's account — its presence implies you track across apps, even if your code never calls requestTrackingAuthorization.

Internally the Swift SDK uses Swift Concurrency: events are queued in an actor-isolated buffer (RAM-only), flushed on a 30-second timer and on UIApplication.willResignActiveNotification. Force-quit before flush drops queued events — by design. There is no UserDefaults or file backing.

How this compares to other analytics SDKs

Subscription renewalFirebase AnalyticsMixpanelRespectlytics
Per-user MRR attributionYesYesNo (use billing system)
Plan tier as parameterRecommendedRecommendedUse distinct event_name
Renewal count (1st, 2nd, …) as parameterRecommendedRecommendedOut of scope
Per-cohort retention curvePer-userPer-userUse billing system
Renewal *rate* by country / platformYesYesYes (default aggregation)

Frequently asked questions

Should we fire renewal events client-side or server-side?

Server-side, ideally — billing webhooks are authoritative and don't depend on the user opening the app. Use the Respectlytics REST API from your webhook handler, with the same event_name a client would have used.

What's the right event name when we have multiple plan tiers?

Distinct names per tier: subscription_renewed_basic, subscription_renewed_pro. The aggregation gives you per-tier renewal rate. Don't pass the tier as a custom parameter — the API rejects it.

Do we still need event-level renewal tracking if we have billing webhooks?

Maybe not. If your only renewal questions are "how many renewed and how much did they pay", your billing system is enough. Respectlytics adds value when you want to correlate in-app behavior (engagement events, feature usage) with renewal — that needs a session-scoped signal.

What about renewals after a brief lapse (grace period)?

Apple and Google have explicit grace-period states. If you fire renewal events for them, use a distinct name: subscription_renewed_after_grace. Distinguish them downstream because their behavior is different.

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.