Skip to main content
Deep links allow users to navigate directly to specific content within your app by clicking on a URL. This guide walks you through the complete setup, from creating your verification config files and saving them in Linkrunner to making the necessary code changes in your app. There are two primary approaches to deep linking:
  1. HTTP/HTTPS Deep Links: URLs with http:// or https:// protocols that can open your app when clicked. Requires domain verification.
  2. Custom URI Schemes: URLs with a custom protocol like myapp:// that are registered to your app. No verification needed.

HTTP/HTTPS Deep Linking

HTTP/HTTPS deep links (including App Links on Android and Universal Links on iOS) require you to prove domain ownership before they work reliably. The setup has four parts:
  1. Create your verification config files
  2. Save them in Linkrunner
  3. Update native configuration (Android & iOS)
  4. Configure your app’s navigation

Step 1: Create Verification Config Files

Create a file named assetlinks.json with the following content:
[
    {
        "relation": ["delegate_permission/common.handle_all_urls"],
        "target": {
            "namespace": "android_app",
            "package_name": "your.package.name",
            "sha256_cert_fingerprints": ["SHA-256:XX:XX:XX:..."]
        }
    }
]
Replace:
  • your.package.name with your actual Android package name
  • SHA-256:XX:XX:XX:... with your app’s SHA-256 fingerprint

Get your app’s SHA-256 fingerprint

For debug builds:
keytool -list -v -keystore ~/.android/debug.keystore -alias androiddebugkey -storepass android -keypass android
For release builds:
keytool -list -v -keystore your_release_keystore.keystore -alias your_key_alias
Look for the “SHA-256 Certificate fingerprint” line in the output.
Debug and release builds are signed with different keystores, so they produce different SHA-256 fingerprints. App Links verification will only succeed for the build whose fingerprint is currently saved in Linkrunner.Before testing or shipping a release build, either:
  • Update the sha256_cert_fingerprints value in Linkrunner to your release keystore’s fingerprint, or
  • List both fingerprints in the array so debug and release builds both verify:
"sha256_cert_fingerprints": [
    "SHA-256:DEBUG:FINGERPRINT:...",
    "SHA-256:RELEASE:FINGERPRINT:..."
]
If you use Google Play App Signing, get the release fingerprint from Play Console → Setup → App integrity, not your local upload keystore.

Step 2: Save Verification Config in Linkrunner

Linkrunner hosts your verification files automatically so you don’t have to manage server configuration yourself.
  1. Log in to your Linkrunner dashboard
  2. Go to Project Settings from the navigation menu
  3. In the Domain Verification section:
    • Paste your apple-app-site-association JSON in the iOS (Only JSON allowed) text area
    • Paste your assetlinks.json content in the Android (Only JSON allowed) text area
  4. Click Save
Linkrunner will automatically host these files at:
  • iOS: https://your-domain.io/.well-known/apple-app-site-association
  • Android: https://your-domain.io/.well-known/assetlinks.json

Step 3: Update Native Configuration

These changes are the same whether you’re using React Native or Flutter.
Open android/app/src/main/AndroidManifest.xml and add the following inside the <activity> section:
<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" />
  <!-- Your domain and subdomains -->
  <data android:scheme="https" android:host="example.com" />
  <data android:scheme="https" android:host="app.example.com" />
  <data android:scheme="https" android:host="store.example.com" />
</intent-filter>

Enable install detection (Continue to app popup)

To let the Linkrunner redirect page detect that your app is installed and show a Continue to app popup instead of sending the user to the store, declare an asset_statements link back to your subdomain.Add the statement to android/app/src/main/res/values/strings.xml:
<string name="asset_statements" translatable="false">
  [{\"relation\": [\"delegate_permission/common.handle_all_urls\"],
    \"target\": {\"namespace\": \"web\", \"site\": \"https://your.subdomain.com\"}}]
</string>
Then reference it from AndroidManifest.xml, inside the <application> section:
<meta-data
  android:name="asset_statements"
  android:resource="@string/asset_statements" />
Replace https://your.subdomain.com with your Linkrunner subdomain. This is the app→site half of the Digital Asset Link. Linkrunner already hosts the site→app half at /.well-known/assetlinks.json on your subdomain.

Step 4: Configure Navigation

React Native uses React Navigation for handling deep links.
// App.js or your navigation configuration file
import { NavigationContainer } from "@react-navigation/native";
import { createStackNavigator } from "@react-navigation/stack";
import LinkRunner from "@linkrunner/react-native";

const Stack = createStackNavigator();

function App() {
    const linking = {
        prefixes: [
            "https://example.com",
            "https://app.example.com",
            "https://store.example.com",
        ],
        config: {
            screens: {
                Home: "",
                Profile: "profile/:id",
                Store: {
                    path: "store/:category?",
                    parse: {
                        category: (category) => category || "all",
                    },
                },
                "app.example.com": {
                    screens: {
                        AppSpecificScreen: ":id",
                    },
                },
                "store.example.com": {
                    screens: {
                        StoreSpecificScreen: ":id",
                    },
                },
            },
        },
    };

    return (
        <NavigationContainer linking={linking}>
            <Stack.Navigator>{/* Your screens */}</Stack.Navigator>
        </NavigationContainer>
    );
}

export default App;

Custom URI Schemes

Custom URI schemes use a custom protocol like myapp:// and don’t require domain verification. They’re useful for backward compatibility or simpler setups.

Native Configuration

Open android/app/src/main/AndroidManifest.xml and add inside the <activity> section:
<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="myapp" />
</intent-filter>

Use adb to test:
# Test HTTP/HTTPS deep links
adb shell am start -a android.intent.action.VIEW -d "https://app.example.com/profile/123" your.package.name

# Test custom URI scheme
adb shell am start -a android.intent.action.VIEW -d "myapp://profile/123" your.package.name
If the HTTPS link opens the browser instead of your app, see Debugging Domain Verification.

Debugging Domain Verification

If an HTTPS link opens the browser instead of your app, domain verification is the usual cause. Work through these checks to find where it’s failing.
1

Check the hosted file

Confirm the file Linkrunner hosts for you is live and correct:
curl https://your-domain.io/.well-known/assetlinks.json
If it returns a 404, your config isn’t saved. Re-save it in Project Settings → Domain Verification. If it loads, check that package_name matches your app and sha256_cert_fingerprints includes the fingerprint of the keystore that signed the build you’re testing.You can also have Google’s Digital Asset Links API validate it, which surfaces formatting errors:
curl "https://digitalassetlinks.googleapis.com/v1/statements:list?source.web.site=https://your-domain.io&relation=delegate_permission/common.handle_all_urls"
2

Check the verification state

On Android 12+, print the verification state of every domain in your manifest:
adb shell pm get-app-links your.package.name
your.package.name:
    ID: 01234567-89ab-cdef-0123-456789abcdef
    Signatures: [F2:52:4D:82:...]
    Domain verification state:
      example.com: verified
      app.example.com: 1024
StateMeaning
verifiedVerification passed. Links for this domain open your app.
noneVerification hasn’t run yet. It runs automatically about 20 seconds after install.
1024 (or higher) / legacy_failureVerification failed. Check the fingerprint and hosted file.
approvedManually approved, e.g. by the user in app settings.
deniedManually disallowed by the user in app settings.
On Android 11 and below, run adb shell dumpsys package domain-preferred-apps instead, and watch adb logcat | grep IntentFilter during install for verification messages.
3

Clear the cached state and re-verify

Android caches verification results, so fixing assetlinks.json has no effect until verification runs again. Clear the cached state and re-run it:
# Clear the cached verification state for all domains
adb shell pm set-app-links --package your.package.name 0 all

# Re-run verification against the hosted assetlinks.json
adb shell pm verify-app-links --re-verify your.package.name

# Wait a few seconds, then check the result
adb shell pm get-app-links your.package.name
Reinstalling the app also triggers a fresh verification pass.
4

Force-approve the domain while debugging (optional)

To test your in-app navigation before verification passes, approve the domains manually. This is equivalent to the user enabling Open supported links in app settings:
adb shell pm set-app-links-user-selection --user cur --package your.package.name true all
If links open your app after this but verification still fails, the problem is in the hosted file or fingerprint, not your app code.

Troubleshooting

Release builds are signed with a different keystore, so they have a different SHA-256 fingerprint. If you use Google Play App Signing, the production fingerprint comes from Play Console → Setup → App integrity, not your local keystore. Add the release fingerprint to sha256_cert_fingerprints in Linkrunner alongside the debug one.
Both platforms cache verification results:
  • Android: clear the cached state with adb shell pm set-app-links --package your.package.name 0 all, then re-verify with adb shell pm verify-app-links --re-verify your.package.name.
  • iOS: wait for Apple’s CDN to refresh (up to a day) or enable developer mode, then delete and reinstall the app.
Check that the scheme is registered (intent filter on Android, URL Types in Xcode on iOS) and that the app is installed on the device. Use lowercase schemes everywhere; Android matches them case-sensitively.
Need help? Contact support@linkrunner.io