Blogs/Technology

How to Implement Deep Linking in Android with Branch.io

Written byMurtuza Kutub
Aug 10, 2026
11 Min Read
How to Implement Deep Linking in Android with Branch.io Hero
Too Long? Read This First
- A deep link opens a specific destination inside an application.
- Android App Links use verified HTTPS domains and can open the app without displaying an app chooser.
- Branch links can route users to the app, an app store, or a web fallback.
- Deferred deep links preserve link data through installation when the required attribution signals are available.
- Add the Branch SDK, configure the manifest, and initialize a Branch session.
- Use custom metadata such as content_id to determine which screen to open.
- Test installed, uninstalled, foreground, and invalid-link scenarios separately.
- Never place the Branch Secret inside the Android application.

A shared link should take users directly to the relevant content, not merely open the app’s home screen.

For example, tapping a shared product link should open that product inside the installed app. If the app is not installed, the user may be redirected to Google Play and taken to the same product after installation. This second behaviour is known as deferred deep linking.

Branch provides the link routing and deferred deep-linking infrastructure required to support these journeys without building the entire system from scratch.

What Is Mobile Deep Linking?

Mobile deep linking is a navigation method that uses a URL to open a specific screen or piece of content inside a mobile application.

A standard application launch might open the home screen:

Open app → Home

A deep link can open a specific destination:

Tap article link → App opens → Article 452

If the app is not installed, a deferred deep link can support a longer journey:

Tap article link
→ Google Play
→ Install app
→ Open app
→ Article 452

Without deep linking, the application may open successfully but leave users to search for the content themselves.

These terms describe related but different behaviours.

Link typeBehaviour
Deep linkOpens a destination inside an installed app using a URI scheme or URL
Android App LinkUses a verified HTTPS domain associated with the Android app
Deferred deep linkAttempts to preserve the intended destination through app installation
Web fallbackOpens a corresponding webpage when the app cannot handle the link
Deep link
Behaviour
Opens a destination inside an installed app using a URI scheme or URL
1 of 4

A custom URI might look like this:

exampleapp://article/452

An Android App Link uses a regular HTTPS URL:

https://example.app.link/article/452

Verified App Links are generally preferable because they use standard HTTPS URLs and can open the associated application without presenting an app-selection dialog.

How Branch Deep Linking Works

A Branch link contains routing information and optional custom parameters.

When a user taps the link, Branch evaluates factors such as:

  • The device platform
  • Whether the application is installed
  • The configured Android routing rules
  • Store and web fallback settings
  • Parameters attached to the link

The resulting journey may be:

Branch link
├── App installed → Open the requested app content
├── App unavailable → Open Google Play
└── Unsupported platform → Open the configured web fallback

After the application opens, the Branch SDK returns the link parameters. The application remains responsible for mapping those parameters to an actual screen.

In practice, we prefer passing a stable identifier such as content_id instead of placing a complete object inside the link. The destination can then retrieve the latest content from its repository or API.

Step 1: Configure the Android App in Branch

Create or select the application in the Branch dashboard, then configure its Android settings.

The required values normally include:

  • Android package name
  • Google Play URL or custom fallback URL
  • Branch link domain
  • Android URI scheme
  • SHA-256 signing-certificate fingerprint
  • Live and test Branch keys

Use a URI scheme that is unlikely to conflict with another application:

exampleapp://

Custom URI schemes are useful as a fallback, but verified HTTPS App Links should be the primary routing mechanism on supported Android versions.

Step 2: Get the SHA-256 Fingerprint

Generate the fingerprint for a local signing key with keytool:

keytool -list -v -keystore /absolute/path/to/your-release-key.jks

Copy the SHA-256 value into the Android App Links configuration in Branch.

If the application uses Google Play App Signing, use the app-signing certificate fingerprint from Google Play Console for production. The certificate used locally for uploading the app may not be the certificate Google Play uses to sign the version installed on users’ devices.

This mismatch is one of the most common reasons App Links work in a local build but fail after release.

Step 3: Add the Branch Android SDK

Add the Branch SDK and Google Play Install Referrer library to the app module:

dependencies {
    implementation("io.branch.sdk.android:library:5.+")
    implementation("com.android.installreferrer:installreferrer:2.2")
}

The 5.+ notation follows the current major Branch Android SDK line. For a production application, pin the exact stable version being tested instead of allowing Gradle to select future releases automatically.

Step 4: Create the Application Class

Create a custom Application class and initialize Branch:

import android.app.Application
import io.branch.referral.Branch

class DeepLinkApplication : Application() {

    override fun onCreate() {
        super.onCreate()

        Branch.getAutoInstance(this)
    }
}

Register it in AndroidManifest.xml:

<application
    android:name=".DeepLinkApplication"
    android:allowBackup="true"
    android:label="@string/app_name"
    android:theme="@style/Theme.App">

    <!-- Activities and Branch configuration go here. -->

</application>

Keep Branch logging disabled in production unless it is temporarily required for diagnosis. Link metadata can contain internal routing information that should not be unnecessarily written to production logs.

Build Seamless User Experiences Across Platforms

F22 Labs helps you design connected mobile experiences that improve onboarding, retention, and user satisfaction.

Step 5: Configure AndroidManifest.xml

Add the Branch keys inside the <application> element:

<meta-data
    android:name="io.branch.sdk.BranchKey"
    android:value="key_live_REPLACE_WITH_LIVE_KEY" />

<meta-data
    android:name="io.branch.sdk.BranchKey.test"
    android:value="key_test_REPLACE_WITH_TEST_KEY" />

<meta-data
    android:name="io.branch.sdk.TestMode"
    android:value="false" />

Use the live key in production and the test key during controlled testing.

The Branch Secret is a server-side credential. Do not add it to the manifest, application resources, source code, or mobile build.

Configure the activity that receives incoming links:

<activity
    android:name=".MainActivity"
    android:exported="true"
    android:launchMode="singleTask">

    <intent-filter>
        <action android:name="android.intent.action.MAIN" />
        <category android:name="android.intent.category.LAUNCHER" />
    </intent-filter>

    <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="example.app.link" />

        <data
            android:scheme="https"
            android:host="example-alternate.app.link" />
    </intent-filter>

    <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="exampleapp" />
    </intent-filter>

</activity>

Replace the example hosts with the link domains assigned in Branch.

android:autoVerify="true" asks Android to verify that the domain is authorized to open the application. Branch hosts the required Digital Asset Links configuration for properly configured Branch domains.

Keep test domains in a debug-specific manifest where possible. Adding an incorrectly configured hostname to the production intent filter can interfere with App Link verification.

singleTask allows an existing MainActivity instance to receive a new link through onNewIntent() instead of creating another copy of the activity.

Step 7: Initialize the Branch Session

Initialize the Branch session in MainActivity.onStart():

import android.content.Intent
import android.os.Bundle
import android.util.Log
import androidx.appcompat.app.AppCompatActivity
import io.branch.referral.Branch
import io.branch.referral.BranchError
import org.json.JSONObject

class MainActivity : AppCompatActivity() {

    private val branchListener =
        Branch.BranchReferralInitListener { params, error ->
            handleBranchResult(params, error)
        }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
    }

    override fun onStart() {
        super.onStart()

        Branch.sessionBuilder(this)
            .withCallback(branchListener)
            .withData(intent?.data)
            .init()
    }

    override fun onNewIntent(newIntent: Intent) {
        super.onNewIntent(newIntent)

        intent = newIntent

        Branch.sessionBuilder(this)
            .withCallback(branchListener)
            .reInit()
    }

    private fun handleBranchResult(
        params: JSONObject?,
        error: BranchError?
    ) {
        if (error != null) {
            Log.e("BranchDeepLink", error.message)
            return
        }

        val clickedBranchLink =
            params?.optBoolean("+clicked_branch_link", false) == true

        if (!clickedBranchLink) {
            return
        }

        val contentId = params
            ?.optString("content_id")
            ?.takeIf { it.isNotBlank() }

        if (contentId != null) {
            openContent(contentId)
        }
    }

    private fun openContent(contentId: String) {
        // Navigate using NavController, an Intent,
        // or the routing mechanism used by the app.
    }
}

onStart() handles the initial application launch, while onNewIntent() handles a new Branch link received by an activity that is already running.

The +clicked_branch_link check is important. Branch session initialization can also run during a normal app launch, so the application should not assume every callback represents a clicked deep link.

Step 8: Route the User to the Correct Screen

The link parameter should be translated into an internal application route.

For a Navigation Component application:

private fun openContent(contentId: String) {
    val navHostFragment = supportFragmentManager
        .findFragmentById(R.id.nav_host_fragment)
        as NavHostFragment

    val navController = navHostFragment.navController

    val args = bundleOf(
        "content_id" to contentId
    )

    navController.navigate(
        R.id.contentDetailsFragment,
        args
    )
}

Required imports:

import androidx.core.os.bundleOf
import androidx.navigation.fragment.NavHostFragment

Do not navigate before the navigation host is ready. In applications with authentication or asynchronous startup, temporarily store the requested ID and complete navigation after startup and access checks finish.

We have seen otherwise-correct deep links fail because routing occurs too early—not because Branch failed to return the parameters.

A Branch Universal Object describes the content being shared:

import android.content.Context
import io.branch.indexing.BranchUniversalObject
import io.branch.referral.util.ContentMetadata
import io.branch.referral.util.LinkProperties

fun createContentLink(
    context: Context,
    contentId: String,
    title: String,
    description: String,
    onResult: (String?) -> Unit
) {
    val metadata = ContentMetadata()
        .addCustomMetadata("content_id", contentId)

    val content = BranchUniversalObject()
        .setCanonicalIdentifier("content/$contentId")
        .setTitle(title)
        .setContentDescription(description)
        .setContentMetadata(metadata)

    val linkProperties = LinkProperties()
        .setChannel("app")
        .setFeature("sharing")
        .addControlParameter(
            "\$fallback_url",
            "https://www.example.com/content/$contentId"
        )

    content.generateShortUrl(
        context,
        linkProperties
    ) { url, error ->
        onResult(if (error == null) url else null)
    }
}

Call the function from an Activity:

createContentLink(
    context = this,
    contentId = "452",
    title = "Example content",
    description = "Open this content in the app"
) { url ->
    if (url != null) {
        shareLink(url)
    }
}

This implementation explicitly receives a Context, so it works as a top-level function and does not depend on an implicit Activity reference.

The link includes:

  • A stable canonical identifier
  • A title and description
  • A custom content_id
  • Channel and feature metadata
  • A web fallback URL

The fallback should point to a safe, valid location. Avoid constructing redirect destinations directly from untrusted user input.

After generating the URL, share it through an Android ACTION_SEND Intent:

private fun shareLink(url: String) {
    val shareIntent = Intent(Intent.ACTION_SEND).apply {
        type = "text/plain"
        putExtra(
            Intent.EXTRA_TEXT,
            "Open this content: $url"
        )
    }

    startActivity(
        Intent.createChooser(
            shareIntent,
            "Share link"
        )
    )
}

The receiving application gets a standard HTTPS link. Its eventual destination depends on the user’s device, installation state, and the routing rules configured for that Branch link.

How Deferred Deep Linking Works

A normal deep link works when the application is already installed.

A deferred deep link attempts to retain the link context while the user installs the application:

User taps Branch link
→ Redirected to Google Play
→ Installs the application
→ Opens it for the first time
→ Branch returns the original link parameters
→ Application opens the intended content

The routing code does not need a separate content_id format for deferred links. The same Branch callback receives the link parameters when attribution succeeds.

However, deferred deep linking should not be treated as guaranteed under every condition. Browser behaviour, privacy restrictions, unavailable attribution signals, installation source, and device settings may affect matching.

Always provide a safe default destination when the expected parameter is unavailable.

Pass Identifiers, Not Complete Objects

Prefer:

content_id = 452

Avoid embedding complete user, product, or article objects inside a link. The application can retrieve the latest data after opening.

Validate Incoming Parameters

A deep link is external input. Check that identifiers have an expected format before using them:

val contentId = params
    ?.optString("content_id")
    ?.takeIf { it.matches(Regex("[A-Za-z0-9_-]{1,64}")) }

The backend must still enforce authorization. A valid link must not allow users to access content they are not permitted to view.

Handle Authentication

If the destination requires authentication:

Deep link received
→ Store pending destination
→ Ask user to sign in
→ Validate access
→ Open destination

Do not discard the link simply because the user is currently signed out.

Provide a Fallback

If the content no longer exists, show a clear error and allow the user to continue into the application.

Avoid leaving users on a blank screen or repeatedly retrying the same failed destination.

Do Not Block Startup Indefinitely

Network-based deep-link resolution can be delayed. The application should still have a normal startup path instead of keeping users on a splash screen indefinitely while waiting for a callback.

Test more than the successful installed-app path.

Build Seamless User Experiences Across Platforms

F22 Labs helps you design connected mobile experiences that improve onboarding, retention, and user satisfaction.

Test With the App Installed

Open a Branch link from a messaging application, email, browser, or ADB:

adb shell am start \
  -W \
  -a android.intent.action.VIEW \
  -d "https://example.app.link/your-link"

Confirm that:

  • The application opens directly.
  • The correct destination appears.
  • The expected custom parameters are received.

Test While the App Is Already Open

Keep the application in the foreground and tap another Branch link.

This verifies that onNewIntent() and reInit() process the new destination correctly.

Test the Deferred Flow

For a controlled test:

  1. Create a Branch test link.
  2. Remove the application from the test device.
  3. Tap the link.
  4. Install the application through the configured store flow.
  5. Open the application.
  6. Confirm that the intended destination is restored.

Use a designated test device and Branch’s testing tools to avoid contaminating production attribution data.

Ask Android to verify the package again:

adb shell pm verify-app-links \
  --re-verify com.example.app

Inspect the current verification state:

adb shell pm get-app-links \
  com.example.app

Replace com.example.app with the application’s package name.

Common Branch Deep-Linking Problems

Check that:

  • android:autoVerify="true" is present.
  • The Branch domain exactly matches the manifest host.
  • The production SHA-256 fingerprint is configured.
  • The user has not disabled supported links for the app.
  • Every declared App Link domain is correctly verifiable.

Android Displays an App Chooser

The domain may not be verified, or the link may be using a custom URI scheme shared by another application.

Prefer verified HTTPS App Links for production navigation.

The App Opens, but the Wrong Screen Appears

Confirm that the link contains the expected custom key and that the application checks +clicked_branch_link before routing.

Also verify that navigation is not occurring before the app’s navigation host or authentication state is ready.

Ensure that the receiving activity uses singleTask, assigns the new Intent in onNewIntent(), and calls reInit() to process the incoming Branch link.

Deferred Deep Linking Does Not Work

Check the store configuration, Branch test mode, Install Referrer dependency, package name, signing fingerprints, and whether the application was installed through the expected test journey.

The release build may use a different signing certificate. If Google Play App Signing is enabled, configure the fingerprint shown in Google Play Console rather than relying only on the local upload key.

Frequently Asked Questions

What is Branch.io deep linking?

Branch deep linking uses configurable HTTPS links to route users to specific in-app content, an app store, or a web fallback based on platform, installation state, and link settings.

Does Branch work if the Android app is not installed?

Branch can redirect users to Google Play and attempt to restore the original destination after installation. This deferred flow depends on correct SDK, store, signing, and link configuration.

Include a stable content identifier and only the routing metadata the application needs. Avoid sensitive personal data, authentication tokens, complete records, or information that should not appear in a shared URL.

Not exactly. Android App Links are verified HTTPS links associated with an Android application. Branch links can use App Links while also providing routing, attribution, deferred deep linking, and fallback behaviour.

The application’s domain association may not be verified. Check the manifest host, autoVerify setting, SHA-256 signing fingerprint, Branch configuration, and Android’s supported-link settings for the application.

Should the Branch Key be stored in AndroidManifest.xml?

Yes, the Branch Key is used by the mobile SDK and is added as manifest metadata. The Branch Secret is server-side and must never be included in the Android application.

Treat the link as a navigation request, not proof of authorization. Authenticate the user, validate permissions through the backend, and open the requested destination only when access is allowed.

Author-Murtuza Kutub
Murtuza Kutub
LinkedIn

A product development and growth expert, helping founders and startups build and grow their products at lightning speed with a track record of success. Apart from work, I love to Network & Travel.

Share this article

Phone

Next for you

8 Best GraphQL Libraries for Node.js in 2025 Cover

Technology

Aug 4, 202613 min read

8 Best GraphQL Libraries for Node.js in 2025

8 Best GraphQL Libraries for Node.js in 2026 Too Long? Read This First - Choose Apollo Server when you need a mature ecosystem, GraphOS integration, plugins, or Apollo Federation. - Choose GraphQL Yoga for a modern, portable server with Fetch API compatibility and built-in support for subscriptions over Server-Sent Events. - Choose Mercurius when your application already uses Fastify and runtime efficiency is a major priority. - Use GraphQL.js when you need the official JavaScript implementati

9 React Native Animation Libraries and Tools Compared Cover

Technology

Aug 4, 202615 min read

9 React Native Animation Libraries and Tools Compared

Too Long? Read This First - Use React Native Reanimated for gesture-driven, interruptible, and performance-sensitive interface animations. - Use the built-in Animated API for simple fades, transforms, and timed sequences without another dependency. - Pair React Native Gesture Handler with Reanimated for swipes, dragging, pinching, rotation, and other touch-driven experiences. - Use Lottie React Native for non-interactive motion graphics supplied by designers. - Choose React Native Skia for cust

9 Critical Practices for Secure Web Application Development Cover

Technology

Aug 4, 202616 min read

9 Critical Practices for Secure Web Application Development

Too Long? Read This First - Define security requirements and model threats before implementation begins. - Treat authentication, account recovery, and MFA as one complete identity system. - Apply server-side authorization to every protected action and object. - Prevent injection with parameterized APIs, structured validation, safe output handling, and restricted outbound requests. - Protect sessions and tokens throughout their complete lifecycle. - Minimise sensitive data and manage encryption