How to Implement Deep Linking in Android with Branch.io

- 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 → HomeA deep link can open a specific destination:
Tap article link → App opens → Article 452If the app is not installed, a deferred deep link can support a longer journey:
Tap article link
→ Google Play
→ Install app
→ Open app
→ Article 452Without deep linking, the application may open successfully but leave users to search for the content themselves.
Deep Links vs App Links vs Deferred Deep Links
These terms describe related but different behaviours.
| Link type | Behaviour |
| Deep link | Opens a destination inside an installed app using a URI scheme or URL |
| Android App Link | Uses a verified HTTPS domain associated with the Android app |
| Deferred deep link | Attempts to preserve the intended destination through app installation |
| Web fallback | Opens a corresponding webpage when the app cannot handle the link |
A custom URI might look like this:
exampleapp://article/452An Android App Link uses a regular HTTPS URL:
https://example.app.link/article/452Verified 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 fallbackAfter 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.jksCopy 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.
Step 6: Add an Android App Links Intent Filter
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.NavHostFragmentDo 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.
Step 9: Create a Branch Deep Link in Kotlin
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.
Step 10: Share the Generated Link
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 contentThe 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.
Deep-Link Routing Best Practices
Pass Identifiers, Not Complete Objects
Prefer:
content_id = 452Avoid 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 destinationDo 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.
How to Test Branch Deep Links
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:
- Create a Branch test link.
- Remove the application from the test device.
- Tap the link.
- Install the application through the configured store flow.
- Open the application.
- Confirm that the intended destination is restored.
Use a designated test device and Branch’s testing tools to avoid contaminating production attribution data.
Verify Android App Links
Ask Android to verify the package again:
adb shell pm verify-app-links \
--re-verify com.example.appInspect the current verification state:
adb shell pm get-app-links \
com.example.appReplace com.example.app with the application’s package name.
Common Branch Deep-Linking Problems
The Link Opens in a Browser
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.
The Link Works Only When the App Is Closed
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.
Debug Links Work, but Production Links Fail
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.
What data should a Branch link contain?
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.
Are Branch links the same as Android App Links?
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.
Why does my Branch link open in the browser?
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.
How should protected deep-link destinations be handled?
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.



