Deep links require a Pro plan subscription or higher.
Deferred deep linking allows you to track which link a user came from even when they don’t have your app installed.
Deferred deep linking on Dub
When a user clicks a link without the app installed, they’re redirected to the app store. After installing and opening the app, you can retrieve the original link information and redirect them to the appropriate screen.

Android Play Store

Android provides the Install Referrer API which allows you to retrieve information about how a user came to install your app, including the referrer URL. Here is how it works in a nutshell:
  1. User taps a deep link on a device without your app installed
  2. Dub redirects the user to the App Store or Play Store by using device targeting
  3. User installs your app from the app store
  4. App reads the install referrer on first launch
  5. App extracts the deep link from the referrer URL and tracks the deep link open event using the /track/open endpoint
  6. Redirect the user to the appropriate screen using the destination URL returned by the /track/open endpoint

Understanding the Referrer URL Structure

When Dub redirects users to the Play Store, the referrer URL contains the deep link information in a nested structure. The referrer URL looks like this:
https://play.google.com/store/apps/details?id=com.example.app&referrer=deepLink%3Dhttps%253A%252F%252Fdub.sh%252Fgps
To extract the original deep link, you need to:
  1. Parse the referrer parameter from the Play Store URL
  2. URL decode it to get the deepLink parameter
  3. URL decode the deepLink value to get the actual deep link
The code examples below show how to implement this extraction process.

Step 1: Add the Install Referrer dependency

First, you’ll need to add the Google Play Install Referrer library to your project.
// package.json
{
  "dependencies": {
    "react-native-play-install-referrer": "latest"
  }
}

Step 2: Implement the Install Referrer logic

Now you’ll need to implement the logic to read the install referrer, extract the deep link, and track the deep link open.
// InstallReferrerTracker.js
import { PlayInstallReferrer } from "react-native-play-install-referrer";

class InstallReferrerTracker {
  constructor() {
    this.isFirstLaunch = true;
  }

  trackInstallReferrer() {
    // Check if this is the first launch
    if (!this.isFirstLaunch) {
      return;
    }

    PlayInstallReferrer.getInstallReferrerInfo((installReferrerInfo, error) => {
      if (!error) {
        console.log(
          "Install referrer = " + installReferrerInfo.installReferrer
        );

        if (installReferrerInfo.installReferrer) {
          // Extract the deep link from the referrer URL
          const deepLink = this.extractDeepLinkFromReferrer(
            installReferrerInfo.installReferrer
          );
          if (deepLink) {
            // Track the deep link open with the extracted URL
            this.trackDeepLinkOpen(deepLink);
          }
        }
      } else {
        console.log("Failed to get install referrer info!");
        console.log("Response code: " + error.responseCode);
        console.log("Message: " + error.message);
      }

      this.isFirstLaunch = false;
    });
  }

  extractDeepLinkFromReferrer(referrerUrl) {
    try {
      // Parse the referrer URL to extract the deep link
      // e.g. for referrer=deepLink%3Dhttps%253A%252F%252Fdub.sh%252Fgps
      // the deep link is https://dub.sh/gps
      const referrerUrlObj = new URL(referrerUrl);
      const deepLinkParam = referrerUrlObj.searchParams.get("deepLink");
      return decodeURIComponent(deepLinkParam);
      return null;
    } catch (error) {
      console.error("Error extracting deep link from referrer:", error);
      return null;
    }
  }

  async trackDeepLinkOpen(deepLink) {
    try {
      const response = await fetch("https://api.dub.co/track/open", {
        method: "POST",
        headers: {
          "Content-Type": "application/json",
        },
        body: JSON.stringify({
          deepLink,
        }),
      });

      if (response.ok) {
        const data = await response.json();
        const destinationUrl = data.link.url;

        // Navigate to the destination URL in your app
        this.navigateToDestination(destinationUrl);
      }
    } catch (error) {
      console.error("Error tracking deep link open:", error);
    }
  }

  navigateToDestination(destinationUrl) {
    // Implement your navigation logic here
    // This will depend on your navigation library (React Navigation, etc.)
    console.log("Navigating to:", destinationUrl);
  }
}

export default InstallReferrerTracker;

Step 3: Initialize the tracker in your app

Now you need to initialize the Install Referrer tracker when your app starts.
// App.js
import React, { useEffect } from 'react';
import InstallReferrerTracker from './InstallReferrerTracker';

const App = () => {
  useEffect(() => {
    const tracker = new InstallReferrerTracker();

    // Track install referrer on app launch
    tracker.trackInstallReferrer();
  }, []);

  return (
    // Your app components
  );
};

export default App;

Step 4: Handle the navigation

Finally, implement the navigation logic to redirect users to the appropriate screen based on the destination URL.
// InstallReferrerTracker.js (navigation method)
navigateToDestination(destinationUrl) {
  // Parse the destination URL to determine which screen to navigate to
  const url = new URL(destinationUrl);
  const path = url.pathname;

  // Example navigation logic
  if (path.includes('/product/')) {
    const productId = path.split('/product/')[1];
    // Navigate to product detail screen
    navigation.navigate('ProductDetail', { productId });
  } else if (path.includes('/category/')) {
    const categoryId = path.split('/category/')[1];
    // Navigate to category screen
    navigation.navigate('Category', { categoryId });
  } else {
    // Navigate to home screen
    navigation.navigate('Home');
  }
}

Important notes

  1. First Launch Detection: The install referrer is only available on the first launch after installation. Make sure to track this properly to avoid duplicate tracking.
  2. URL Decoding: The referrer URL is URL-encoded multiple times. Make sure to properly decode it to extract the original deep link.
  3. Network Operations: Ensure that all API calls are made in background threads to avoid blocking the main thread and ensure smooth app performance.
  4. Error Handling: Always implement proper error handling for network requests and URL parsing.
  5. Testing: Test your implementation thoroughly using the Google Play Console’s internal testing track.
To get started, we recommend using the quickstart guide to set up your deep links on Dub.

iOS App Store

Unlike Android, iOS doesn’t provide a built-in install referrer API. Dub implements a hybrid approach combining deterministic tracking (clipboard-based) and probabilistic tracking (IP-based) to ensure reliable deferred deep linking on iOS.

How iOS deferred deep linking works on Dub

Dub’s iOS deferred deep linking solution provides two tracking approaches:
  1. Deterministic clipboard-based tracking: Uses iOS clipboard data to reliably identify and open the exact deep link the user interacted with.
  2. Probabilistic IP-based tracking: Matches users from the web to the app based on their IP address.
For the deterministic approach, when an iOS user who doesn’t have your app installed clicks on your deep link, Dub redirects them to a custom landing page:
iOS deferred deep linking landing page
This page displays two options:
  • Get the App: Copies the deep link to the clipboard before redirecting to the App Store.
  • Get the App without Copying: Redirects directly to the App Store without copying the deep link to the clipboard.
After the user installs your app, the deep link resolution method depends on the button they clicked.

1. Deterministic clipboard-based tracking

This method uses the iOS clipboard to pass the deep link into your app after installation, ensuring reliable and direct link resolution. Here is how it works in a nutshell:
  1. User taps Get the App button on the landing page.
  2. Dub copies the deep link URL to the clipboard.
  3. The user is redirected to the App Store to download your app.
  4. After installation, your app reads the clipboard to retrieve the deep link.
  5. Your app calls the /track/open endpoint with the deepLink parameter in the request body.
  6. Dub returns the destination URL, and your app navigates the user to the appropriate screen (see deep links quickstart for more details).

2. Probabilistic IP-based tracking

This method relies on matching the user’s device IP address from the initial click event to the app open event after installation.
  1. User taps Get the App without Copying button on the landing page.
  2. The user is redirected directly to the App Store to download your app.
  3. Dub has already tracked the click and stored the IP address at the time of deep link click.
  4. After installation, your app calls the /track/open endpoint with the dubDomain parameter in the request body.
  5. Dub matches the user using IP-based tracking and returns the destination URL.
  6. If a destination URL is returned, your app navigates the user to the appropriate screen (see deep links quickstart for more details).
The following logic determines whether to use clipboard-based tracking or IP-based tracking:
  • Clipboard-based tracking: Used when a deep link is found in the clipboard (e.g., acme.link). The app sends the deepLink parameter in the request body.
  • IP-based tracking: Used when no deep link is found in the clipboard. The app sends only the dubDomain parameter, allowing Dub to match the user based on their IP address.
This ensures the most reliable tracking method is chosen automatically.
// App.js
import React, { useEffect } from 'react';
import Clipboard from '@react-native-clipboard/clipboard';
import AsyncStorage from '@react-native-async-storage/async-storage';

export default function App() {
  useEffect(() => {
    trackOpen();
  }, []);

  return (
    // Your app components
  );
}

// Make request to /track/open endpoint
async function trackOpen() {
  try {
    // Check if this is first launch
    const hasLaunched = await AsyncStorage.getItem('app_first_launch');
    if (hasLaunched !== null) {
      return;
    }
    await AsyncStorage.setItem('app_first_launch', 'false');

    // Check clipboard for deep link
    const clipboard = await Clipboard.getString();
    let requestBody;

    if (clipboard && clipboard.includes('acme.link')) {
      // Clipboard-based tracking
      requestBody = { deepLink: clipboard };
    } else {
      // IP-based tracking fallback
      requestBody = { dubDomain: 'acme.link' };
    }

    const response = await fetch('https://api.dub.co/track/open', {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
      },
      body: JSON.stringify(requestBody),
    });

    if (response.ok) {
      const data = await response.json();
      const destinationURL = data.link?.url;

      if (destinationURL) {
        // Navigate to the destination URL
        console.log('Navigating to:', destinationURL);
      }
    }
  } catch (error) {
    console.error('Error tracking open:', error);
  }
}

Handle the navigation

Once you have the deep link URL from your trackOpen function, you can route the user to the appropriate screen.
// App.js with React Navigation
import React, { useEffect, useRef } from "react";
import { NavigationContainer } from "@react-navigation/native";
import { createNativeStackNavigator } from "@react-navigation/native-stack";

const Stack = createNativeStackNavigator();

export default function App() {
  const navigationRef = useRef();

  useEffect(() => {
    const handleDeepLink = async () => {
      const destinationURL = await trackOpen();

      if (destinationURL && navigationRef.current) {
        // Parse URL and navigate
        const url = new URL(destinationURL);
        const path = url.pathname;

        if (path.includes("/product/")) {
          const productId = path.split("/product/")[1];
          navigationRef.current.navigate("Product", { productId });
        } else {
          navigationRef.current.navigate("Home");
        }
      }
    };

    handleDeepLink();
  }, []);

  return (
    <NavigationContainer ref={navigationRef}>
      <Stack.Navigator initialRouteName="Home">
        <Stack.Screen name="Home" component={HomeScreen} />
        <Stack.Screen name="Product" component={ProductScreen} />
      </Stack.Navigator>
    </NavigationContainer>
  );
}

Important notes

  1. IP-based Tracking Limitations: IP-based tracking relies on the device’s network IP, which can be less reliable in environments like corporate networks, VPNs, or shared Wi-Fi.
  2. Network Operations: Ensure that all API calls are made in background threads to avoid blocking the main thread and ensure smooth app performance.
  3. Error Handling: Implement proper error handling for network requests, clipboard access to ensure the app behaves gracefully under failures.
  4. Testing: Test thoroughly using TestFlight or an internal distribution build to confirm the flow works for both clipboard-based and IP-based tracking scenarios.
  5. App Store Guidelines: Ensure your app complies with Apple’s App Store guidelines regarding clipboard access and user privacy.