iOS Clipboard Deep Links

Detect deferred deep links from the iOS clipboard using the Smler React Native SDK. Supports pattern matching, wildcard domains, and iCloud Private Relay compatibility.


On iOS, the Smler React Native SDK detects deferred deep links by reading the device clipboard. When a user clicks a Smler short link before installing the app, the link is copied to the clipboard on your landing page. After the user installs and opens the app, the SDK reads the clipboard and matches it against your allowed URL patterns.

How It Works

  1. A user clicks a deep link on the web (e.g. https://go.yourdomain.com/abc123).

  2. Your landing page copies the full URL to the clipboard using JavaScript, then redirects the user to the App Store.

  3. The user installs and opens your app.

  4. On first launch, your app calls getInstallReferrerIos() with a list of allowed URL patterns.

  5. The SDK reads the clipboard and checks if the text matches any of your patterns.

  6. If it matches, the SDK returns the full deep link along with parsed query parameters and path information.

This approach works even under iCloud Private Relay, since it does not rely on IP-based fingerprinting or tracking cookies.

Basic Usage

import { SmlerDeferredLink } from '@smler/deferred-link';
import type { IosClipboardDeepLinkResult } from '@smler/deferred-link';

const result: IosClipboardDeepLinkResult | null =
  await SmlerDeferredLink.getInstallReferrerIos({
    deepLinks: [
      'https://go.yourdomain.com',
      'http://go.yourdomain.com',
      'go.yourdomain.com',
    ],
  });

if (result) {
  console.log('Deep link found:', result.fullDeepLink);
  console.log('Query params:', result.queryParameters);
  console.log('Short code:', result.pathParams.shortCode);
  console.log('DLT header:', result.pathParams.dltHeader);
} else {
  console.log('No matching deep link found on clipboard');
}

Important: This method only works on iOS. Calling it on Android will throw an error: "getInstallReferrerIos() is only intended for iOS."

IosClipboardDeepLinkResult Fields

Field

Type

Description

fullDeepLink

string

The full deep link string exactly as read from the clipboard

fullReferralDeepLinkPath

string

Alias for fullDeepLink

queryParameters

Record<string, string>

Parsed query parameters from the URL (e.g. ?ref=abc&uid=123)

pathParams

PathParams

Extracted shortCode and optional dltHeader from the URL path

PathParams

Field

Type

Description

shortCode

string

The short URL code extracted from the path

dltHeader

string | null

Optional DLT header/campaign identifier

URL Pattern Matching

The deepLinks parameter accepts an array of URL patterns to match against the clipboard text. The matching logic supports several flexible formats:

Supported Pattern Formats

Pattern

Matches

https://example.com

Exact domain (with https scheme)

http://example.com

Exact domain (with http scheme)

example.com

Exact domain (schemeless)

example.com/profile

Domain with specific path prefix

*.example.com

Wildcard subdomain — matches sub.example.com, go.example.com, etc.

example.com/*

Domain with any path

example.com/profile/*

Domain with any path under /profile/

*

Global wildcard — matches any clipboard text that parses as a valid URL

Matching Rules

  • Scheme normalization: Both the clipboard text and the pattern are stripped of http:// and https:// before comparison.

  • www. stripping: The www. prefix is stripped from both sides. So www.example.com matches example.com and vice versa.

  • Subdomain matching: Any subdomain of the pattern's base domain is matched. go.example.com will match a pattern of example.com.

  • Wildcard host: *.example.com matches example.com itself and any subdomain.

  • Wildcard path: A trailing /* will match any path after the fixed prefix.

Example With Multiple Patterns

const result = await SmlerDeferredLink.getInstallReferrerIos({
  deepLinks: [
    'https://go.yourdomain.com/profile',
    'http://go.yourdomain.com/profile',
    'go.yourdomain.com/profile',
    'go.yourdomain.com',                // base domain
    '*.yourdomain.com/profile/*',       // wildcard subdomain + path
  ],
});

Handling the Result

The method returns null in these cases:

  • The clipboard is empty or contains only whitespace

  • The clipboard text does not match any of the provided patterns

  • The clipboard text cannot be parsed as a valid URL

  • Clipboard reading fails (e.g. permission denied)

When a match is found, the full URL is parsed and you receive:

  • The complete deep link string

  • All query parameters as a key-value object

  • The shortCode and dltHeader extracted from the URL path

Once you have the deep link from the clipboard, resolve it to get the original URL and full metadata:

if (result) {
  const data = await SmlerDeferredLink.resolveDeepLink(
    result.fullReferralDeepLinkPath,
    { triggerWebhook: true }
  );

  console.log('Original URL:', data.originalUrl);
  console.log('Short code:', data.shortCode);
  console.log('Domain:', data.domain);

  // Navigate to the correct screen based on data.originalUrl
}

Falling Back to Probabilistic Matching

If getInstallReferrerIos() returns null (no matching clipboard link), you can fall back to probabilistic matching:

import { SmlerDeferredLink, HelperReferrer } from '@smler/deferred-link';

const result = await SmlerDeferredLink.getInstallReferrerIos({
  deepLinks: ['go.yourdomain.com'],
});

if (result) {
  // Clipboard match found — resolve and navigate
  const data = await SmlerDeferredLink.resolveDeepLink(result.fullDeepLink, {
    triggerWebhook: true,
  });
  // Navigate based on data
} else {
  // No clipboard match — try probabilistic matching
  const match = await HelperReferrer.getProbabilisticMatch({
    domain: 'go.yourdomain.com',
  });

  if (match.matched && (match.score ?? 0) > 0.65) {
    console.log('Probabilistic match:', match.pathParams?.shortCode);
    // Navigate based on match.pathParams
  }
}

See the Probabilistic Matching guide for full details on the fallback.

Full iOS Example

import { Platform } from 'react-native';
import AsyncStorage from '@react-native-async-storage/async-storage';
import { SmlerDeferredLink, HelperReferrer } from '@smler/deferred-link';

const DOMAIN = 'go.yourdomain.com';

async function handleIosDeferredAttribution() {
  if (Platform.OS !== 'ios') return;

  // Only run on first install
  const hasRun = await AsyncStorage.getItem('smler_first_install');
  if (hasRun !== null) return;

  try {
    // 1. Check clipboard for a matching deep link
    const result = await SmlerDeferredLink.getInstallReferrerIos({
      deepLinks: [
        `https://${DOMAIN}/profile`,
        `http://${DOMAIN}/profile`,
        `${DOMAIN}/profile`,
        DOMAIN,
      ],
    });

    if (result) {
      // 2. Clipboard match found — resolve it
      console.log('Clipboard deep link:', result.fullDeepLink);
      const data = await SmlerDeferredLink.resolveDeepLink(
        result.fullReferralDeepLinkPath,
        { triggerWebhook: true }
      );
      // Navigate to screen based on data.originalUrl
    } else {
      // 3. Fallback to probabilistic matching
      console.log('No clipboard match — trying probabilistic...');
      const match = await HelperReferrer.getProbabilisticMatch({
        domain: DOMAIN,
      });
      if (match.matched && (match.score ?? 0) > 0.65) {
        // Navigate based on match.pathParams
      }
    }
  } catch (error) {
    console.error('iOS deferred attribution failed:', error);
  }

  await AsyncStorage.setItem('smler_first_install', 'done');
}

Published with LeafPad