Mobile apps

Attribute app installs and signups on iOS, Android and React Native: referral deep links, the Play Install Referrer, deferred matching, App Tracking Transparency and the privacy manifest.

Embed
Preview of the card
<a href="https://referalli.com/docs/mobile"><img src="https://referalli.com/docs/_api/public/orgs/07f432f4-34b9-46a7-aaad-c0e395d2e4f9/spaces/2bf4f614-3630-4fbf-85f9-5ecafa6b7d97/pages/acb5d6d0-2565-4c88-915d-c44df7cf47be/social-card.svg?v=3e7ae33eec8b" alt="Mobile apps" width="600" height="315"></a>

The mobile SDKs find out which partner referred an install, then attribute the user's signup to that partner. They hold only your public tracking token (tk_..., from Settings → Tracking or GET /v1/org), never an rk_ key.

Platform

Package

Install

iOS, iPadOS, macOS (Swift)

Referalli from referalli-swift

SwiftPM, from the 0.1.0 tag

Android (Kotlin or Java)

com.referalli:referalli-android

Gradle; Maven Central is coming soon, see SDKs

React Native and Expo

@referalli/react-native

npm install @referalli/react-native

How an install is attributed

The SDK returns the referral from the first of these that applies:

  1. A link. A partner's referral link (https://referalli.com/go/<token>/<code>) redirects to the destination set on the link, adding ?via=<code>&ref_click_id=<click>. When that destination is a universal link (iOS) or App Link (Android) of your app, the app opens, and the SDK stores the click id: deterministic attribution. Opening a /go/ URL inside the app also works; the SDK records the click itself.

  2. The Play Install Referrer (Android, first open). Point the link's destination at your Play listing, with the referral inside referrer: https://play.google.com/store/apps/details?id=com.example&referrer=via%3D<code>. On first open the SDK reads ref_click_id, via or ref from it. With only a code, it records the click through the API, so the signup still attributes deterministically.

  3. A deferred match (first open, within an hour of the click). The app wasn't installed when the link was tapped, so the user went through the store. The API matches the first open's IP address to a recent click and returns its click id and destination, so you can deep-link the user where the link pointed.

Steps 2 and 3 run once per install. They are skipped when the app was installed more than 24 hours before the SDK first ran: that is an existing user updating, not a referred install. A link opened later always replaces the stored referral.

Then, after signup, call trackSignup(customerRef) with your user id, the same id your billing provider sees. The SDK sends the install's anonymous id and the referral, and the API creates the referral.

iOS (Swift)

Add the package in Xcode (File → Add Package Dependencies…, https://github.com/Referalli/referalli-swift) or in Package.swift:

dependencies: [
    .package(url: "https://github.com/Referalli/referalli-swift", from: "0.1.0"),
],
targets: [
    .target(name: "App", dependencies: [.product(name: "Referalli", package: "referalli-swift")]),
]
import SwiftUI
import Referalli

@main
struct MyApp: App {
    init() {
        // The tracking token is public and safe to ship in the app.
        Referalli.configure(publishableKey: "tk_...", programId: "prg_...")
    }

    var body: some Scene {
        WindowGroup {
            ContentView()
                .onOpenURL { Referalli.handle(url: $0) }                        // custom-scheme links
                .onContinueUserActivity(NSUserActivityTypeBrowsingWeb) {          // universal links
                    Referalli.handle(userActivity: $0)
                }
                .task {
                    if let referral = await Referalli.getReferral() {             // nil when not referred
                        print("invited with code", referral.code ?? "-")
                    }
                }
        }
    }
}

// After the user signs up:
let result = try await Referalli.trackSignup(customerRef: user.id)
print("attributed:", result.attributed)

Referalli.setReferral(code:) stores a code the user typed into an invite-code field. For tests, ReferalliTracker takes a ReferalliMemoryStore and a stub ReferalliTransport.

Universal links. Add the applinks:<your-domain> associated domain to your app's entitlements, and serve /.well-known/apple-app-site-association for your app on that domain. Then set the destination of your referral links to a URL on that domain.

Android

// build.gradle.kts
dependencies {
    implementation("com.referalli:referalli-android:0.1.0")
}
import com.referalli.Referalli
import com.referalli.android.configure
import com.referalli.android.handleIntent

class App : Application() {
    override fun onCreate() {
        super.onCreate()
        Referalli.configure(this, publishableKey = "tk_...", programId = "prg_...")
    }
}

class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        Referalli.handleIntent(intent)          // opened from a referral link
        Referalli.getReferral { referral, _ ->  // called on the main thread
            referral?.destination?.let { openDeepLinkedScreen(it) }
            referral?.code?.let { showInvitedBy(it) }
        }
    }

    override fun onNewIntent(intent: Intent) {
        super.onNewIntent(intent)
        Referalli.handleIntent(intent)
    }
}

// After the user signs up:
Referalli.trackSignup(user.id) { result, error ->
    if (error != null) Log.w("referalli", "signup not recorded", error)
    else Log.i("referalli", "attributed=${result?.attributed}")
}

From Java, use ReferalliAndroid.configure(context, "tk_...", "prg_...") and ReferalliAndroid.handleIntent(intent); the callbacks are ReferalliCallback<T>. The Install Referrer library (com.android.installreferrer:installreferrer) comes in transitively; pass useInstallReferrer = false to skip it.

App Links. Add an autoVerify intent filter for your domain to the activity in AndroidManifest.xml, and serve /.well-known/assetlinks.json with your app's signing certificate fingerprint:

<intent-filter android:autoVerify="true">
    <action android:name="android.intent.action.VIEW" />
    <category android:name="android.intent.category.DEFAULT" />
    <category android:name="android.intent.category.BROWSABLE" />
    <data android:scheme="https" android:host="app.example.com" />
</intent-filter>

Requirements: minSdk 21, compileSdk 35, a Java 17 toolchain, and Kotlin 2.4 or newer if you call the Kotlin API. Network calls use HttpURLConnection; pass transport = OkHttpTrackerTransport(okHttpClient) to ReferalliAndroid.configure to use your OkHttp client.

React Native and Expo

npm install @referalli/react-native
cd ios && pod install   # bare React Native

With Expo, add the config plugin and use a development build. Expo Go has no native module, so there the referral is kept in memory only.

{
  "expo": {
    "plugins": [["@referalli/react-native", { "domains": ["app.example.com"] }]]
  }
}

domains adds applinks:<domain> to the iOS entitlements and a verified App Links intent filter on Android. You still serve the two .well-known files from that domain. In a bare app, add the same entitlement in Xcode and the intent filter in AndroidManifest.xml.

import Referalli from '@referalli/react-native';

// Once, at startup.
Referalli.configure('tk_...', { programId: 'prg_...' });

// On the first screen: the referral for this install, or null.
const referral = await Referalli.getReferral();
if (referral?.destination) navigateTo(referral.destination);
if (referral?.code) showInvitedBy(referral.code);

// After signup:
const { attributed } = await Referalli.trackSignup(user.id);

The SDK listens to Linking itself (listenForLinks: false turns that off). Also available: handleUrl(url) for URLs that arrive outside Linking (push notifications), setReferral(code) for an invite-code field, addReferralListener(fn) for links opened while the app runs, resolveDeferred() and reset(). It supports React Native 0.71+ (old and new architecture), iOS 15.1+ and Android minSdk 21. A complete Expo app is react-native-expo in referalli-examples.

Purchases are reported from your server

The app holds only the public token, so it cannot create commissions. Send the purchase to your backend, validate the App Store or Google Play receipt there, then report it with a secret key:

import { Referalli } from 'referalli';

const referalli = new Referalli(); // REFERALLI_API_KEY
await referalli.mobile.purchases({
  eventId: receipt.transactionId, // processed once
  customerRef: user.id,
  amountMinor: 999,
  currency: 'usd',
  productRef: 'pro_monthly',
});

For a refund, send type: 'refund' with the refunded amount. Every server SDK has the same call (post_mobile_purchases, PostMobilePurchases, postMobilePurchases); see SDKs.

Privacy, App Tracking Transparency and the privacy manifest

What the SDKs collect. A random install id (anonId, not derived from the device) and the captured referral, stored on the device (iOS UserDefaults, an Android private SharedPreferences file). They send these, plus the customerRef you pass to trackSignup, to Referalli. They never read the IDFA, the IDFV, the Android advertising id, the Android id or the clipboard, and they don't link AppTrackingTransparency.

Links and the install referrer are deterministic. The user opened the link or installed from it; no fingerprinting is involved.

Deferred matching is probabilistic. It compares the IP address of the first app open with recent link clicks. Apple defines tracking as linking data from your app with data from other companies' apps or websites for targeted advertising or advertising measurement, and doesn't allow fingerprinting even with consent. Whether referral attribution counts depends on your program: invite-a-friend links on your own domain are first-party, while paid affiliate placements on third-party sites can count as advertising measurement. If you are unsure:

  • configure with deferred matching off (deferredResolution: false in Swift and React Native, deferredResolution = false on Android);

  • call resolveDeferred() only after the user has granted permission through your own ATTrackingManager.requestTrackingAuthorization prompt, and declare tracking in your own privacy manifest and App Store labels; or don't call it at all.

Links and the install referrer keep working with deferred matching off.

The privacy manifest. The Swift package and the React Native iOS module each ship a PrivacyInfo.xcprivacy, which Xcode merges into your app's privacy report. It declares:

Key

Value

NSPrivacyTracking

false, with no tracking domains

Collected data

User ID (your customerRef) and Device ID (the random install id), linked to the user, not used for tracking, for App functionality and Analytics

Required-reason API

NSPrivacyAccessedAPICategoryUserDefaults, reason CA92.1

Mirror these in your App Store privacy labels. If you enable deferred matching after an ATT prompt, your app's own manifest must declare the tracking.

Google Play Data safety. The Android SDK's share is Device or other IDs (the random install id) and User IDs (your customer reference), collected for App functionality and Analytics, processed by Referalli on your behalf, and not used for advertising. The deferred match also uses the IP address of the request.

Updated