Navigation
Documentation Updated April 2026

HopLinks Developer Guide

Welcome to the HopLinks Developer Guide. This manual provides comprehensive instructions for integrating HopLinks—our intelligent Dynamic and Deferred Deep Linking engine—directly into your mobile ecosystem without the need for proprietary SDKs.

The Modern FDL Alternative

Just like Firebase Dynamic Links, HopLinks routes users to the optimal destination—app, store, or web—using native OS protocols for maximum reliability and zero bloat.

The SDK-Less Philosophy

HopLinks provides a seamless routing experience entirely without a proprietary SDK. Instead of forcing you to bloat your app with third-party libraries, HopLinks leverages the native deep linking protocols built directly into iOS and Android.

iOS: Universal Links (AASA)

Apple devices look for a file at /.well-known/apple-app-site-association. HopLinks automatically hosts this for you, proving app ownership to the OS.

Android: App Links

Android devices verify ownership via /.well-known/assetlinks.json. We host your SHA-256 fingerprints to enable unblockable deep linking.

Intent Link

Standard URI schemes (myapp://). Simple, but only works if the app is already installed.

Dynamic Link

Device-aware smart routing. Detects platform and directs to App, Store, or Web automatically.

1. Dashboard Configuration

Register your application in the HopLinks dashboard to activate your verification endpoints.

HopLinks Setup
1

Navigate to App Settings in your dashboard and create a new configuration.

2

Android: Enter your Package Name and SHA-256 Fingerprint.

3

iOS: Enter your Team ID and Bundle ID.

2. Native Platform Setup

Configure your native project to recognize the HopLinks domain (www.hpln.in).

Android (App Links)

Add the following intent filter to your AndroidManifest.xml:

<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="www.hpln.in" />
</intent-filter>

iOS (Universal Links)

In Xcode, go to Signing & Capabilities and add the Associated Domains capability with:

applinks:www.hpln.in

Note: Use your custom domain if you have one configured in HopLinks.

Step-by-Step React Native CLI Version 1.0 | April 2026

Integration: React Native (CLI)

In Expo, deep linking is mostly configured in app.json. In React Native CLI, you must modify native files for both Android and iOS. This guide walks through every step needed to replicate Expo deep linking behavior in a bare React Native CLI project.

Note

This guide covers custom URI schemes (myapp://) as well as Universal Links (https) for both Android App Links and iOS Universal Links.

1

Android Configuration

Modify AndroidManifest.xml to register your custom URI scheme and Universal Links.

Location: android/app/src/main/AndroidManifest.xml

Add the following intent-filter tags inside the <activity> tag (usually MainActivity):

Custom URI Scheme (myapp://)

<intent-filter android:label="Deep Link">
    <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>

Universal Links (https)

<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="hoplinks.in" />
    <data android:scheme="https" android:host="www.hpln.in" />
</intent-filter>
2

iOS Configuration

Step A: Add URL Scheme in Info.plist

Location: ios/ReactNativeCLIDemo/Info.plist

Add the CFBundleURLTypes key to register your custom URL scheme:

<key>CFBundleURLTypes</key>
<array>
    <dict>
        <key>CFBundleURLSchemes</key>
        <array>
            <string>myapp</string>
        </array>
    </dict>
</array>

Step B: Handle Links in AppDelegate.mm

Location: ios/ReactNativeCLIDemo/AppDelegate.mm

1. Import the Linking Manager

#import <React/RCTLinkingManager.h>

2. Add handling methods (before @end)

- (BOOL)application:(UIApplication *)application
   openURL:(NSURL *)url
   options:(NSDictionary<UIApplicationOpenURLOptionsKey,id> *)options
{
  return [RCTLinkingManager application:application openURL:url options:options];
}

- (BOOL)application:(UIApplication *)application
    continueUserActivity:(nonnull NSUserActivity *)userActivity
    restorationHandler:(nonnull void (^)(NSArray<id<UIUserActivityRestoring>> * _Nullable))restorationHandler
{
  return [RCTLinkingManager application:application
                   continueUserActivity:userActivity
                     restorationHandler:restorationHandler];
}
3

React Navigation Setup (App.tsx)

Define the linking configuration and pass it to the NavigationContainer. This maps URL paths to screen names.

const linking = {
  prefixes: [
    "myapp://",           // Custom URI scheme
    "https://hoplinks.in", // Universal link
  ],
  config: {
    screens: {
      Home:          "home",
      Explore:       "product/:id",  // :id is a dynamic parameter
      Notifications: "notifications",
      Profile:       "profile",
    },
  },
};

// Inside your App component:
<NavigationContainer linking={linking}>
  {/* ... Navigator ... */}
</NavigationContainer>

Tip

The :id in product/:id is a dynamic route parameter. It will be available in your screen component as route.params.id.

4

Testing Your Implementation

Android — using adb

Run the following command to trigger a deep link on a connected Android device or emulator:

adb shell am start -W \
  -a android.intent.action.VIEW \
  -d "myapp://product/42" \
  com.reactnativeclidemo

iOS — using xcrun

Run the following command to trigger a deep link on the iOS Simulator:

xcrun simctl openurl booted "myapp://product/42"

Key Differences: Expo vs React Native CLI

Aspect Expo Go React Native CLI
Configuration app.json only Native files (AndroidManifest.xml, Info.plist, AppDelegate.mm)
URL Scheme exp:// prefix Custom (e.g. myapp://)
Universal Links Handled automatically Manual autoVerify + ASLINKS file
Native Bridge Built-in RCTLinkingManager (manual)

Implementation Checklist

  • AndroidManifest.xml: Added intent-filter for myapp:// URI scheme
  • AndroidManifest.xml: Added intent-filter with autoVerify=true for https Universal Links
  • Info.plist: Added CFBundleURLTypes with myapp scheme
  • AppDelegate.mm: Imported RCTLinkingManager
  • AppDelegate.mm: Added openURL handler method
  • AppDelegate.mm: Added continueUserActivity handler method
  • App.tsx: Defined linking config with prefixes and screen mappings
  • App.tsx: Passed linking prop to NavigationContainer
  • Tested Android deep link via adb
  • Tested iOS deep link via xcrun simctl

Sample Code

View the complete working React Native CLI example project on GitHub.

View Repository
8 Steps Expo ^54 Version 1.0 | May 2025

Integration: React Native (Expo) + Hoplinks

Complete Setup for both Android and iOS platforms.

1

Install Required Library

Add expo-linking to your project. Run the following command inside your project root. Use npx expo install (not plain npm) so Expo selects the version that matches your SDK.

npx expo install expo-linking

NOTE

After installation, the package is automatically added to your package.json with the correct version constraint for your Expo SDK.

2

Configure app.json

Register your URL scheme and verified domains. Open app.json and add the following configuration. This tells iOS and Android that your app is authorised to open specific URLs.

2.1 Set the custom URL scheme (myapp://)

2.2 Register verified HTTPS domains for Android intent filters

2.3 Register associated domains for iOS Universal Links

{
  "expo": {
    "scheme": "myapp",
    "ios": {
      "bundleIdentifier": "com.example.myapp",
      "associatedDomains": [
        "applinks:www.hpln.in",
        "applinks:hoplinks.in"
      ]
    },
    "android": {
      "package": "com.example.myapp",
      "intentFilters": [
        {
          "action": "VIEW",
          "autoVerify": true,
          "data": [
            { "scheme": "https", "host": "www.hpln.in" },
            { "scheme": "https", "host": "hoplinks.in" }
          ]
        }
      ]
    }
  }
}

IMPORTANT

"autoVerify": true tells Android to fetch and verify the assetlinks.json file from your domain. Without it, App Links will silently fall back to the browser.

Always restart the Expo dev server after editing scheme or intentFilters — changes are not hot-reloaded.

3

Configure React Navigation

Map URLs to screens in App.js. Create a linking object and pass it to <NavigationContainer>. This tells React Navigation which screen to open for each incoming URL.

3.1 Import expo-linking and create the URL prefix

3.2 Define the linking config with prefixes + screen paths

3.3 Pass the config to NavigationContainer

import * as Linking from 'expo-linking';
import { NavigationContainer } from '@react-navigation/native';
 
// Step 3.1 — base prefix for Expo Go development URLs
const prefix = Linking.createURL('/');
 
// Step 3.2 — full routing configuration
const linking = {
  prefixes: [
    prefix,              // Expo Go URLs
    'myapp://',          // Custom scheme
    'https://www.hpln.in',
    'https://hoplinks.in',
  ],
  config: {
    screens: {
      Home:          'home',
      Explore:       'product/:id',   // :id is a dynamic param
      Notifications: 'notifications',
      Profile:       'profile',
    },
  },
};
 
// Step 3.3 — pass into NavigationContainer
export default function App() {
  return (
    <NavigationContainer linking={linking}>
      {/* Your Tab.Navigator goes here */}
    </NavigationContainer>
  );
}

URL to screen mapping reference:

Screen Path Pattern Example URL
Home home myapp://home
Explore product/:id myapp://product/id123
Notifications notifications myapp://notifications
Profile profile myapp://profile
4

Read Parameters in Screens

Extract data passed via the deep link. When a user opens myapp://product/id123, React Navigation injects { id: "id123" } as a route parameter. Use the useRoute hook to read it inside your screen component.

import { useRoute } from '@react-navigation/native';
import { View, Text } from 'react-native';
 
export default function ExploreScreen() {
  const route = useRoute();
 
  // Extract the dynamic :id segment from the deep link
  const deepLinkId = route.params?.id;
 
  return (
    <View>
      {deepLinkId && (
        <Text>You opened product: {deepLinkId}</Text>
      )}
    </View>
  );
}

NOTE

React Navigation parses and passes all URL segments and query parameters automatically. You do not need to call Linking.getInitialURL() manually for this.

5

Verify Domain Ownership (Automated)

Hoplinks handles server-side file hosting automatically! You do not need to worry about hosting files yourself. Simply provide your app details in the App Settings from your dashboard, and our system will automatically generate and host the required domain ownership files for Apple and Google.

WHAT HAPPENS BEHIND THE SCENES

Based on your App Settings, Hoplinks dynamically generates and serves the following files at the required paths on your verified domain:

  • Android: /.well-known/assetlinks.json
  • iOS: /.well-known/apple-app-site-association
6

Test the Integration

Verify deep links work before going live. Testing approach depends on whether you are running Expo Go or a native build.

6.1 Testing in Expo Go — use ADB to send a test intent

# Run on a connected Android emulator or device
adb shell am start -W -a android.intent.action.VIEW \
  -d "exp://127.0.0.1:8081/--/product/id123"

IMPORTANT

You cannot test https:// Universal Links inside Expo Go. The OS blocks them because Expo Go's certificate does not match your assetlinks.json.

6.2 Testing Native Builds — use the uri-scheme CLI

# Test the custom scheme
npx uri-scheme open "myapp://product/id123" --android
 
# Test a verified App Link
npx uri-scheme open "https://hoplinks.in/product/id123" --android

TIP

Always fully test myapp:// custom scheme routing before moving on to testing https:// App Links. This isolates navigation issues from domain verification issues.

7

Configure Hoplinks Dashboard

Point your short links to the right in-app screen. Each Hoplinks short link needs an In-App Deep Link value that maps to a screen in your app. Follow these steps in the dashboard:

7.1 Log into your Hoplinks Dashboard

7.2 Click "Create New Link" or edit an existing one

7.3 Set the standard Destination URL (web fallback for users without the app)

7.4 Scroll to the "Deep Link Intent" section

7.5 Enter the custom scheme URI in the "In-App Deep Link" field (see table below)

Dashboard configuration reference:

Target Screen In-App Deep Link value What it opens
Home Screen myapp://home Opens the Home tab
Explore (product) myapp://product/promo-123 Opens Explore with id=promo-123
Notifications myapp://notifications Opens Notifications screen
Profile Screen myapp://profile Opens the Profile screen

How it works under the hood:

  • App installed: Phone intercepts the intent, opens the app, React Navigation routes to the correct screen.
  • App NOT installed: The custom scheme URI fails silently; Hoplinks redirects the user to the App Store or Google Play Store.
8

Handle Deferred Deep Linking

Route users to the right screen on first launch. Deferred Deep Linking solves this scenario: a user clicks a Hoplinks URL, is sent to the App Store because the app is not installed, downloads the app, and opens it for the first time. The app must know which screen to show.

8.1 Add getInitialURL() to your linking config in App.js

const linking = {
  prefixes: [prefix, 'myapp://', 'https://www.hpln.in', 'https://hoplinks.in'],
 
  // NEW — catches the URL that launched the app
  async getInitialURL() {
    // 1. Check if opened via a standard OS deep link
    const url = await Linking.getInitialURL();
    if (url != null) return url;
 
    // 2. (Optional) Call Hoplinks SDK for deferred link data
    // const deferredUrl = await Hoplinks.getDeferredLink();
    // if (deferredUrl) return deferredUrl;
 
    return null;
  },
 
  config: {
    screens: {
      Home:          'home',
      Explore:       'product/:id',
      Notifications: 'notifications',
      Profile:       'profile',
    },
  },
};

8.2 Verify the flow end-to-end

Click a Hoplinks URL on a device that does not have the app installed. Install the app from the store. On first launch, confirm the app opens the correct screen.

NOTE

Without getInitialURL(), users who install via the App Store link will always land on the default Home screen instead of the content they originally clicked.

Quick Reference — Do's & Don'ts

DO

  • Ensure assetlinks.json package_name exactly matches app.json
  • Restart the Expo dev server after changing scheme or intentFilters
  • Use human-readable URL paths (e.g. hoplinks.in/explore/design)
  • Test myapp:// scheme routing with uri-scheme before testing https:// links

DON'T

  • Test https:// links in Expo Go (the OS will block them)
  • Add a .json extension to apple-app-site-association
  • Forget to update the SHA-256 fingerprint when switching to production builds
  • Serve assetlinks.json with HTML content-type or unexpected headers

Sample Code

View the complete working example project on GitHub.

View Repository

Integration: Flutter

Add the app_links package to your pubspec.yaml and initialize the listener:

import 'dart:async';
import 'package:app_links/app_links.dart';

class _MyAppState extends State<MyApp> {
  late AppLinks _appLinks;
  StreamSubscription<Uri>? _linkSubscription;

  @override
  void initState() {
    super.initState();
    initDeepLinks();
  }

  Future<void> initDeepLinks() async {
    _appLinks = AppLinks();

    // Handle deep links when app is in background/foreground
    _linkSubscription = _appLinks.uriLinkStream.listen((uri) {
      print('Received HopLink: $uri');
      _handleLinkRoute(uri);
    });

    // Handle deep link when app opens from cold start
    final initialUri = await _appLinks.getInitialAppLink();
    if (initialUri != null) _handleLinkRoute(initialUri);
  }

  void _handleLinkRoute(Uri uri) {
    if (uri.host == 'www.hpln.in') {
        String code = uri.pathSegments.last;
        print("Navigate to: $code");
    }
  }
}

Integration: Android (Kotlin)

In your MainActivity.kt, override onCreate and onNewIntent to catch the URL parameters.

import android.content.Intent
import android.net.Uri
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity

class MainActivity : AppCompatActivity() {

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

    override fun onNewIntent(intent: Intent?) {
        super.onNewIntent(intent)
        handleDeepLink(intent)
    }

    private fun handleDeepLink(intent: Intent?) {
        val action: String? = intent?.action
        val data: Uri? = intent?.data
        if (Intent.ACTION_VIEW == action && data != null) {
            val code = data.lastPathSegment
            println("Received HopLink code: $code")
        }
    }
}

Integration: Android (Java)

Similar to Kotlin, override the intent handling methods in MainActivity.java.

import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
import androidx.appcompat.app.AppCompatActivity;

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        handleDeepLink(getIntent());
    }

    @Override
    protected void onNewIntent(Intent intent) {
        super.onNewIntent(intent);
        handleDeepLink(intent);
    }

    private void handleDeepLink(Intent intent) {
        String action = intent.getAction();
        Uri data = intent.getData();
        if (Intent.ACTION_VIEW.equals(action) && data != null) {
            String code = data.getLastPathSegment();
            // Route to appropriate activity
        }
    }
}

Integration: iOS (Swift)

For iOS apps using SceneDelegate.swift (iOS 13+), intercept the NSUserActivity:

import UIKit

class SceneDelegate: UIResponder, UIWindowSceneDelegate {

    var window: UIWindow?

    func scene(_ scene: UIScene, continue userActivity: NSUserActivity) {
        if userActivity.activityType == NSUserActivityTypeBrowsingWeb,
           let incomingURL = userActivity.webpageURL {
            handleDeepLink(url: incomingURL)
        }
    }

    func scene(_ scene: UIScene, willConnectTo session: UISceneSession,
               options connectionOptions: UIScene.ConnectionOptions) {
        if let userActivity = connectionOptions.userActivities.first,
           userActivity.activityType == NSUserActivityTypeBrowsingWeb,
           let incomingURL = userActivity.webpageURL {
            handleDeepLink(url: incomingURL)
        }
    }

    private func handleDeepLink(url: URL) {
        let pathComponents = url.pathComponents
        if let code = pathComponents.last {
            print("Navigate to content: \(code)")
        }
    }
}

Troubleshooting

Android

Ensure android:autoVerify="true" is set. It can take up to 20 seconds for Android to verify the JSON file after app installation.

iOS

Ensure your Apple Team ID and Bundle ID precisely match what is configured in Xcode. iOS downloads the apple-app-site-association file upon app installation.

Important: Tap, Don't Paste

Universal Links and App Links will not trigger if you paste the URL into a browser's address bar. They must be triggered by a "click" action from an external app (Notes, WhatsApp, Mail).

Need Expert Assistance?

Our integration engineers can help you configure Universal Links, App Links, or complex routing logic for your specific tech stack.