Tutorials

    Deep Linking — Appsflyer

    A
    AzeemFeb 18, 2026 · 11 min read
    151
    Deep Linking — Appsflyer

    Deep Linking — From Zero to Senior Level

    Let me teach you this like you've never heard these words before. We'll build up slowly.

    LEVEL 1 — Before We Even Talk About Deep Links

    First, understand what a "link" does on web

    You're on Google. You click this link:

    https://amazon.in/shoes/nike-air-max
    
    code

    Your browser opens Amazon. And it goes directly to that Nike shoe page.

    That's just a normal web link. Simple.

    Now the question is —

    Can the same thing happen inside a mobile app?

    Can I click a link and land directly on a specific screen inside Amazon app — not the home screen?

    Yes. That's exactly what Deep Linking is.

    What happens WITHOUT deep linking

    User clicks link
    → Amazon app opens
    → Home screen shows
    → User has to search manually
    → User gets frustrated
    → User leaves
    
    code

    What happens WITH deep linking

    User clicks link
    → Amazon app opens
    → DIRECTLY on Nike shoe page
    → User sees what they wanted
    → User buys
    
    code

    Same app. Completely different experience.

    LEVEL 2 — How Does Android Even Open an App From a Link?

    This is where we get technical. But stay with me — it's simpler than it sounds.

    Everything in Android is an Intent

    In Android, when something happens — a button tap, a notification tap, a link click — Android wraps that event in something called an Intent.

    Think of Intent as an envelope.

    ┌─────────────────────────────┐
    │         INTENT              │
    │                             │
    │  Action: VIEW               │
    │  Data:   https://amazon.in/ │
    │          shoes/nike-123     │
    └─────────────────────────────┘
    
    code

    Android delivers this envelope to your app.

    Your app opens it, reads the URL, and decides — "okay, show the shoe screen."

    That's the entire magic. Literally.

    How does Android know WHICH app to send the Intent to?

    You declare it in your AndroidManifest.xml.

    You're basically telling Android:

    "Hey, if anyone sends an Intent with this URL pattern — give it to me."

    <activity android:name=".MainActivity">
    
        <intent-filter>
            <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="amazon.in"
                android:pathPrefix="/shoes" />
        </intent-filter>
    
    </activity>
    
    xml

    Reading this simply:

    • scheme="https" → the URL starts with https://

    • host="amazon.in" → the domain is amazon.in

    • pathPrefix="/shoes" → URL contains /shoes

    When all three match → Android sends the Intent to this Activity.

    What does your Activity do with it?

    class MainActivity : AppCompatActivity() {
    
        override fun onCreate(savedInstanceState: Bundle?) {
            super.onCreate(savedInstanceState)
    
            // Open the envelope
            val intent = intent
            val data: Uri? = intent.data
    
            if (data != null) {
                // Read the URL
                val path = data.path  // "/shoes/nike-123"
                
                // Navigate to correct screen
                if (path?.startsWith("/shoes") == true) {
                    openShoeScreen(path)
                }
            }
        }
    }
    
    kotlin

    That's it at the basic level. You read the URL. You navigate. Done.

    LEVEL 3 — The 3 Types of Deep Links

    Now you understand the concept. Let's talk about the 3 ways to actually implement it. Each one is an evolution of the previous.

    Type 1 — Custom URI Scheme (The Old Way)

    Instead of https://, you invent your own scheme.

    amazon://product/nike-123
    
    code

    This is like a private road only your app knows about.

    Manifest declaration:

    <data android:scheme="amazon" />
    
    xml

    Why it was used: Simple. Easy. No setup needed.

    Why you should NOT use it today:

    Problem 1 — No web fallback

    If the app isn't installed and someone clicks amazon://product/123 in a browser — the browser has no idea what to do. It just shows an error. Dead end.

    Problem 2 — App chooser popup

    If two apps both declare amazon:// — Android shows a popup asking the user "which app do you want to open this with?" Terrible experience.

    Problem 3 — Security

    Any app on the device can declare amazon:// and steal your deep links. There's no ownership verification.

    Type 2 — Android App Links (The Right Way)

    Instead of a custom scheme, you use a real HTTPS URL.

    https://amazon.in/product/nike-123
    
    code

    This is a normal web URL. But when Amazon app is installed — it opens in the app, not the browser. No popup. Directly.

    How does Android trust that Amazon owns amazon.in?

    This is the key question. The answer is called Digital Asset Links.

    Amazon hosts a JSON file on their server:

    https://amazon.in/.well-known/assetlinks.json
    
    code

    That file says:

    [{
      "relation": ["delegate_permission/common.handle_all_urls"],
      "target": {
        "namespace": "android_app",
        "package_name": "com.amazon.mShop.android.shopping",
        "sha256_cert_fingerprints": ["AB:CD:EF:..."]
      }
    }]
    
    json

    Reading this simply:

    "The app with this package name and this certificate fingerprint is allowed to handle URLs from amazon.in"

    Android checks this file when your app is installed. If it matches — your app is verified as the owner of that domain.

    Result:

    • No app chooser popup

    • Opens directly in app

    • Secure — no other app can steal it

    This is what all major apps use today.

    Type 3 — Deferred Deep Link (The Advanced Marketing Case)

    Now here's the tricky one. Pay attention.

    The problem it solves:

    User sees Instagram ad for Nike shoes
    User clicks the ad link
    User does NOT have Amazon app installed
    User is sent to Play Store
    User installs Amazon
    Amazon opens
    → WHERE does the user land?
    
    code

    Without deferred deep linking → Home screen. The context is lost.

    With deferred deep linking → Directly on Nike shoes page. Context preserved.

    But how? The app wasn't even installed when the link was clicked.

    This is genuinely hard. Android has no built-in solution. This is where AppsFlyer comes in. We'll explain the full mechanism in Level 5.

    LEVEL 4 — Handling Deep Links Properly in Code

    Now let's get into real implementation problems that actually trip people up.

    The Cold Start vs Warm Start Problem

    This is one of the most common bugs in production.

    Cold Start = App is not running at all. User clicks link. App launches fresh.

    In this case, the deep link arrives via onCreate():

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        handleDeepLink(intent)  // intent has the link data
    }
    
    kotlin

    Warm Start = App is already running in the background. User clicks link. App comes to foreground.

    In this case, onCreate() is NOT called again. The deep link arrives via onNewIntent():

    override fun onNewIntent(intent: Intent) {
        super.onNewIntent(intent)
        handleDeepLink(intent)  // must handle here too
    }
    
    kotlin

    If you only handle onCreate() and forget onNewIntent() — warm start deep links silently fail.

    The fix is simple — handle both:

    private fun handleDeepLink(intent: Intent) {
        val data: Uri? = intent.data
        if (data != null) {
            navigateBasedOnUri(data)
        }
    }
    
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        handleDeepLink(intent)
    }
    
    override fun onNewIntent(intent: Intent) {
        super.onNewIntent(intent)
        setIntent(intent)       // important — update the activity's intent
        handleDeepLink(intent)
    }
    
    kotlin

    Deep Linking with Jetpack Navigation Component

    Modern apps use Jetpack Navigation. Here's how deep links integrate.

    You declare deep links directly in your nav graph XML:

    <fragment
        android:id="@+id/shoeDetailFragment"
        android:name=".ShoeDetailFragment">
    
        <deepLink
            android:id="@+id/deepLink"
            app:uri="https://amazon.in/shoes/{shoeId}" />
    
    </fragment>
    
    xml

    Now when https://amazon.in/shoes/nike-123 arrives — Navigation Component automatically routes to ShoeDetailFragment and passes shoeId = "nike-123" as an argument.

    You receive it in the Fragment like any other argument:

    val shoeId = arguments?.getString("shoeId")
    
    kotlin

    The important rule: Handle the Intent at the Activity level and pass it to your NavController. Do not try to handle deep links inside individual Fragments — this creates back stack issues and breaks on configuration changes.

    // In MainActivity
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        
        val navController = findNavController(R.id.nav_host_fragment)
        navController.handleDeepLink(intent)  // let NavController handle it
    }
    
    kotlin

    Deep Linking in Jetpack Compose

    Compose uses a slightly different approach with NavHost:

    NavHost(navController = navController, startDestination = "home") {
    
        composable(
            route = "shoes/{shoeId}",
            deepLinks = listOf(
                navDeepLink { 
                    uriPattern = "https://amazon.in/shoes/{shoeId}" 
                }
            )
        ) { backStackEntry ->
            val shoeId = backStackEntry.arguments?.getString("shoeId")
            ShoeDetailScreen(shoeId = shoeId)
        }
    }
    
    kotlin

    Critical rule in Compose: Your NavController must be created at the top level and passed down. Never create it inside a Composable that might recompose — deep link handling will break.

    LEVEL 5 — How AppsFlyer and Deferred Deep Linking Works Internally

    Now we go deep. This is senior-level territory.

    First — What is the Install Referrer API?

    Google Play has a built-in mechanism called the Install Referrer API.

    Here's what it does:

    When someone clicks a link with campaign data and installs your app through Play Store — Play Store preserves the referrer string through the install process.

    Example link:

    https://play.google.com/store/apps/details
    ?id=com.amazon.app
    &referrer=campaign%3Dramadan_shoes%26product%3Dnikeair
    
    code

    After install, your app can query Play Store:

    val referrerClient = InstallReferrerClient.newBuilder(context).build()
    
    referrerClient.startConnection(object : InstallReferrerStateListener {
        override fun onInstallReferrerSetupFinished(responseCode: Int) {
            val response = referrerClient.installReferrer
            val referrerUrl = response.installReferrer
            // "campaign=ramadan_shoes&product=nikeair"
            // Now you know where this install came from
        }
    })
    
    kotlin

    AppsFlyer uses this internally to recover attribution data on first launch.

    The Full Deferred Deep Link Flow

    Let's walk through it step by step with a real scenario.

    Step 1 — User clicks the ad

    The ad has an AppsFlyer link:

    https://app.appsflyer.com/com.amazon.app
    ?pid=facebook
    &c=ramadan_campaign
    &deep_link_value=shoes/nike-123
    
    code

    Step 2 — AppsFlyer server logs the click

    AppsFlyer records:

    Click received
    Time: 2:34 PM
    Campaign: ramadan_campaign
    Source: facebook
    Deep link value: shoes/nike-123
    Device fingerprint: {IP, device model, OS version, screen size}
    
    code

    Step 3 — User goes to Play Store and installs

    Play Store preserves the referrer. App gets installed.

    Step 4 — First launch of the app

    AppsFlyer SDK inside your app wakes up and contacts AppsFlyer servers:

    "Hey, this app just launched for the first time.
    Here's my device fingerprint."
    
    code

    Step 5 — Server matches install to click

    AppsFlyer compares:

    • Install fingerprint vs stored click fingerprints

    • Referrer string from Play Store

    If they match → Attribution confirmed.

    Server responds:

    {
      "campaign": "ramadan_campaign",
      "source": "facebook",
      "deep_link_value": "shoes/nike-123",
      "status": "attributed"
    }
    
    json

    Step 6 — Your app receives the callback

    AppsFlyerLib.getInstance().init(AF_DEV_KEY,
        object : AppsFlyerConversionListener {
    
            override fun onConversionDataSuccess(data: Map<String, Any>) {
                val deepLinkValue = data["deep_link_value"] as? String
                // "shoes/nike-123"
    
                if (deepLinkValue != null) {
                    navigateTo(deepLinkValue)  // open shoe screen
                }
            }
    
            override fun onConversionDataFail(error: String) {
                // attribution failed, open home screen
            }
        },
        this
    )
    
    kotlin

    User lands on the Nike shoe page — even though the app wasn't installed when they clicked the link.


    What if Install Referrer Fails? — Probabilistic Matching

    Sometimes the referrer data is lost (privacy settings, certain devices). AppsFlyer falls back to probabilistic matching.

    It compares the device fingerprint from the click event with the fingerprint from the install event:

    Click fingerprint:   IP=103.x.x.x, Device=Samsung S23, OS=Android 14, Time=2:34 PM
    Install fingerprint: IP=103.x.x.x, Device=Samsung S23, OS=Android 14, Time=2:41 PM
    
    code

    Same IP, same device, 7 minutes apart → High probability this is the same user → Attribution confirmed.

    Not 100% accurate, but works well enough for marketing purposes.

    LEVEL 6 — Security (What Most Devs Ignore)

    Deep links are an attack surface. Here's what can go wrong.

    Parameter Tampering

    Someone sends a user this link:

    myapp://payment/confirm?amount=1&discount=99999
    
    code

    If your app blindly processes these parameters — you have a serious bug.

    Rule: Never trust deep link parameters for financial or security-sensitive operations. Always validate server-side.

    // WRONG
    val amount = uri.getQueryParameter("amount")
    processPayment(amount)  // dangerous
    
    // RIGHT
    val orderId = uri.getQueryParameter("orderId")
    // fetch order details from YOUR server using orderId
    // server validates everything
    // then process payment
    
    kotlin

    Intent Hijacking (Custom Scheme Problem)

    Any app can declare myapp:// if you use custom schemes. A malicious app can intercept your deep links.

    Android App Links with domain verification completely prevents this — because only the verified app gets those URLs.

    Expired Links

    Flash sale ended 2 hours ago but the link is still circulating on WhatsApp.

    Always validate time-sensitive links server-side:

    val campaignId = uri.getQueryParameter("campaignId")
    
    // Call your API
    api.validateCampaign(campaignId) { result ->
        if (result.isActive) {
            navigateToOffer()
        } else {
            showExpiredScreen()
        }
    }
    
    kotlin

    LEVEL 7 — Interview Answer Framework

    When an interviewer asks about deep linking, here's the mental structure to follow:

    Start with types — explain the difference between custom scheme, App Links, and deferred deep linking. Mention why App Links are preferred (domain verification, no app chooser, security).

    Go to implementation — manifest declaration, Intent handling, cold vs warm start, NavController integration.

    Add the hard problem — deferred deep linking, how it requires an SDK, how Install Referrer and probabilistic matching work internally.

    Close with production concerns — security validation, expired links, back stack management.

    That progression shows you've actually built and maintained this in production — not just read about it.

    Deep Linking
    android development
    Was this helpful?

    Comments (0)

    No comments yet. Be the first to share your thoughts!

    Leave a Comment

    Get the latest updates

    Join our newsletter and never miss an article.

    By subscribing, you agree to our Privacy Policy and consent to receive updates.

    Ready to Start Learning?

    Join thousands of students building their dream careers

    Explore Courses