Android has a structural advantage for deferred deep linking: the Play Install Referrer API. When a user clicks a link and is redirected to the Google Play Store, Google records a referrer string and delivers it to your app on first open. This creates a deterministic, privacy-friendly attribution channel that does not depend on advertising IDs or fingerprinting.
This guide covers the complete Android implementation from click to first-open routing, including the Play Install Referrer API, the Tolinku Android SDK, and intent handling for deferred link delivery.
How Android Deferred Deep Linking Works
The flow has three stages:
Click capture. The user taps a Tolinku link in a browser, email, or ad. Tolinku records the click with its parameters (path, campaign, referral code) and redirects the user to the Play Store with a referrer parameter appended to the Play Store URL.
Install. The user installs the app. Google Play records the referrer string.
First open. On first launch, your app queries the Play Install Referrer API. The API returns the referrer string from step one. Your app sends it to Tolinku's server, which uses it to look up the original click and return the deep link data.
On devices where Google Play is unavailable, or where the Install Referrer is not returned (some edge cases with direct APK installs), Tolinku falls back to probabilistic fingerprinting.
Prerequisites
- Android Studio Hedgehog or later
- minSdk 21 (Android 5.0)
- Kotlin 1.9+ or Java 11+
- A Tolinku Appspace with a publishable API key
Add the Tolinku Android SDK to your
build.gradle.kts(app module):dependencies { implementation("com.tolinku.sdk:android:1.0.0") }The full SDK reference is available at tolinku.com/docs/developer/sdks/android/.
Setting Up the Play Install Referrer API Directly
While the Tolinku SDK handles Install Referrer internally, understanding how it works helps with debugging and edge case handling.
Add the Install Referrer library to your dependencies:
implementation("com.android.installreferrer:installreferrer:2.2")Create a client and query the referrer:
import com.android.installreferrer.api.InstallReferrerClient import com.android.installreferrer.api.InstallReferrerStateListener import com.android.installreferrer.api.ReferrerDetails class InstallReferrerHelper(private val context: Context) { private var referrerClient: InstallReferrerClient? = null fun queryReferrer(onResult: (String?) -> Unit) { referrerClient = InstallReferrerClient.newBuilder(context).build() referrerClient?.startConnection(object : InstallReferrerStateListener { override fun onInstallReferrerSetupFinished(responseCode: Int) { when (responseCode) { InstallReferrerClient.InstallReferrerResponse.OK -> { val details: ReferrerDetails? = referrerClient?.installReferrer onResult(details?.installReferrer) referrerClient?.endConnection() } InstallReferrerClient.InstallReferrerResponse.FEATURE_NOT_SUPPORTED -> { onResult(null) } InstallReferrerClient.InstallReferrerResponse.SERVICE_UNAVAILABLE -> { onResult(null) } } } override fun onInstallReferrerServiceDisconnected() { // Optionally retry } }) } }The
installReferrerstring is a URL-encoded query string, for example:utm_source=tolinku&utm_medium=link&tolk_click_id=abc123&utm_campaign=spring_promoParse it with
Uri.parse("http://example.com?" + referrerString)to extract individual parameters.First-Open Detection
Store a flag in
SharedPreferencesto prevent running deferred link resolution on every launch:object InstallState { private const val PREFS_NAME = "tolinku_prefs" private const val KEY_FIRST_OPEN_RESOLVED = "first_open_resolved" fun hasResolvedFirstOpen(context: Context): Boolean { val prefs = context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE) return prefs.getBoolean(KEY_FIRST_OPEN_RESOLVED, false) } fun markFirstOpenResolved(context: Context) { val prefs = context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE) prefs.edit().putBoolean(KEY_FIRST_OPEN_RESOLVED, true).apply() } }Only run deferred resolution when
hasResolvedFirstOpenreturnsfalse.Tolinku SDK Integration

Initialize the SDK in your
Applicationclass:import android.app.Application import com.tolinku.sdk.Tolinku class MyApplication : Application() { override fun onCreate() { super.onCreate() Tolinku.configure( context = this, publishableKey = "tolk_pub_your_key_here" ) } }Register
MyApplicationin yourAndroidManifest.xml:<application android:name=".MyApplication" ...>Handling First Open in Your Launch Activity
In your launcher
Activity, check for a first open and callresolveDeferred:import androidx.appcompat.app.AppCompatActivity import android.os.Bundle import com.tolinku.sdk.Tolinku import com.tolinku.sdk.model.TolinkuDeepLink class MainActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) if (!InstallState.hasResolvedFirstOpen(this)) { resolveFirstOpen() } else { // Normal app launch, handle regular deep link intents handleIncomingIntent(intent) } } private fun resolveFirstOpen() { Tolinku.shared.resolveDeferred(this) { result -> runOnUiThread { InstallState.markFirstOpenResolved(this) result.onSuccess { deepLink -> routeTo(deepLink) }.onFailure { // No matching click found, proceed normally showMainContent() } } } } private fun routeTo(deepLink: TolinkuDeepLink) { // Use deepLink.path, deepLink.params, deepLink.referralCode when { deepLink.path.startsWith("/referral") -> { val intent = Intent(this, ReferralOnboardingActivity::class.java).apply { putExtra("referral_code", deepLink.referralCode) } startActivity(intent) finish() } deepLink.path.startsWith("/promo") -> { val intent = Intent(this, PromoActivity::class.java).apply { putExtra("params", deepLink.params) } startActivity(intent) finish() } else -> showMainContent() } } private fun showMainContent() { // Load default home screen } override fun onNewIntent(intent: Intent?) { super.onNewIntent(intent) intent?.let { handleIncomingIntent(it) } } private fun handleIncomingIntent(intent: Intent) { // Handle regular App Links (non-deferred) here val data = intent.data ?: return // Route based on data.path, data.getQueryParameter(), etc. } }Intent Handling on First Launch
When the app opens via a direct App Link (not a deferred link), Android delivers the URL in the
Intent. On first install, this does not happen unless the user tapped the App Link after installation. Make sure your intent handling logic is inhandleIncomingIntentand is separate from your deferred resolution logic.The key distinction:
- Deferred deep link: App was not installed when the link was tapped. Link data arrives via the Play Install Referrer or fingerprinting on the first open after install.
- Direct deep link: App is already installed when the link is tapped. Android delivers the URL directly in the intent.
Run deferred resolution only once (guarded by
hasResolvedFirstOpen). Handle direct deep links on every launch viaonNewIntentandonCreateintent inspection.Handling Referrer Delays
The Play Install Referrer API is generally available immediately after install, but there can be a brief delay while the Play Store service connects. The Tolinku SDK handles retry logic internally. If you are using the API directly, implement a backoff retry:
private fun queryWithRetry(attempt: Int = 0, onResult: (String?) -> Unit) { if (attempt >= 3) { onResult(null) return } referrerClient?.startConnection(object : InstallReferrerStateListener { override fun onInstallReferrerSetupFinished(responseCode: Int) { if (responseCode == InstallReferrerClient.InstallReferrerResponse.SERVICE_UNAVAILABLE && attempt < 2) { Handler(Looper.getMainLooper()).postDelayed({ queryWithRetry(attempt + 1, onResult) }, 1000L * (attempt + 1)) } else { // Handle result } } override fun onInstallReferrerServiceDisconnected() {} }) }Testing Your Implementation
Android deferred deep link testing can be done with ADB using the
ambroadcast:adb shell am broadcast \ -a com.android.vending.INSTALL_REFERRER \ -n your.package.name/.InstallReferrerReceiver \ --es "referrer" "utm_source=tolinku&tolk_click_id=test123"This simulates the Play Store referrer delivery without needing a real install flow. You can test your routing logic quickly this way.
For a complete end-to-end test:
- Uninstall the app from your test device.
- Tap a Tolinku link (from the test links section of your dashboard) on the device.
- Install the app via the Play Store or ADB.
- Open the app and verify the deferred link data arrives and routes correctly.
- Close and reopen. Confirm deferred resolution does not run again.
Enable debug logging for verbose output:
Tolinku.setLogLevel(Tolinku.LogLevel.DEBUG)Privacy and Advertising IDs
The Google Advertising ID (GAID) is available on Android without requiring explicit permission, though users can opt out in device settings. The Play Install Referrer API does not use the GAID at all; it works through the Play Store's server-side referrer mechanism. This makes it inherently more privacy-friendly than IDFA-based attribution on iOS.
For apps targeting children (COPPA-regulated) or apps where users have opted out of personalized ads, you should not read the GAID. The Tolinku SDK checks the opt-out status automatically and falls back to referrer-only attribution when appropriate.
Google's Privacy Sandbox for Android will eventually change parts of this landscape, but the Play Install Referrer API is expected to remain as a first-party attribution mechanism.
Related Resources
Get deep linking tips in your inbox
One email per week. No spam.