Skip to content
Tolinku
Tolinku
Sign In Start Free
Engineering · · 6 min read

Cross-Platform Referral Links That Work Everywhere

By Tolinku Staff
|
Tolinku webhooks integrations dashboard screenshot for engineering blog posts

A referral link should work the same whether the recipient is on iOS, Android, or web. Tap the link, see the referral content, and attribute the referral to the sender. In practice, each platform handles URLs differently, and the recipient might not have the app installed. This article covers how to build referral links that handle all these cases.

For referral deep link mechanics, see how referral deep links work: end-to-end guide. For building effective referral programs, see building referral programs that actually work.

The Problem

A referral link like https://yourdomain.com/referral/user123 needs to:

  1. Open the native app if installed (iOS or Android).
  2. Navigate to the correct screen with the referrer's ID.
  3. If the app is not installed, take the user to the app store or a landing page.
  4. After installation, still attribute the referral to user123.

Each requirement has platform-specific challenges:

Requirement iOS Android Web
Open app Universal Links App Links N/A
Navigate to screen AppDelegate/SceneDelegate Intent URL routing
App not installed Fallback to App Store Fallback to Play Store Show landing page
Post-install attribution Deferred deep link Deferred deep link Cookie/localStorage

Use a consistent URL format that works across platforms:

https://yourdomain.com/referral/{referrerId}?campaign={campaignId}

The URL is the same for all platforms. Platform detection happens server-side or client-side when the link is opened.

Server-Side Detection

When the URL is opened in a browser (app not installed), detect the platform and redirect:

// Server route handler
app.get('/referral/:referrerId', (req, res) => {
  const { referrerId } = req.params;
  const userAgent = req.headers['user-agent'] || '';

  // If the app handles this via Universal Links / App Links,
  // this handler won't be called. This runs only when the app
  // isn't installed or links aren't verified.

  if (/iPhone|iPad|iPod/.test(userAgent)) {
    // iOS: redirect to App Store with context
    res.redirect(
      `https://apps.apple.com/app/id${APP_STORE_ID}?referrer=${referrerId}`
    );
  } else if (/Android/.test(userAgent)) {
    // Android: redirect to Play Store with referrer
    res.redirect(
      `https://play.google.com/store/apps/details?id=${PACKAGE_NAME}&referrer=${referrerId}`
    );
  } else {
    // Web: show referral landing page
    res.render('referral-landing', { referrerId });
  }
});

Native App Handling

iOS

Configure Universal Links so https://yourdomain.com/referral/* opens the app:

AASA file:

{
  "applinks": {
    "details": [{
      "appIDs": ["TEAMID.com.yourcompany.yourapp"],
      "components": [
        { "/": "/referral/*" }
      ]
    }]
  }
}

Handle in the app:

func application(_ application: UIApplication,
                 continue userActivity: NSUserActivity,
                 restorationHandler: @escaping ([UIUserActivityRestoring]?) -> Void) -> Bool {
    guard let url = userActivity.webPageUrl,
          let components = URLComponents(url: url, resolvingAgainstBaseURL: false) else {
        return false
    }

    let path = components.path
    if path.hasPrefix("/referral/") {
        let referrerId = String(path.dropFirst("/referral/".count))
        handleReferral(referrerId: referrerId)
        return true
    }

    return false
}

func handleReferral(referrerId: String) {
    // Store the referral
    UserDefaults.standard.set(referrerId, forKey: "pending_referrer")

    // Navigate to referral welcome screen
    let vc = ReferralWelcomeViewController(referrerId: referrerId)
    rootNavigationController?.pushViewController(vc, animated: true)
}

Android

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="yourdomain.com"
          android:pathPrefix="/referral" />
</intent-filter>

Handle in the activity:

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    handleReferralIntent(intent)
}

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

private fun handleReferralIntent(intent: Intent) {
    val uri = intent.data ?: return
    if (uri.path?.startsWith("/referral/") == true) {
        val referrerId = uri.lastPathSegment ?: return
        handleReferral(referrerId)
    }
}

private fun handleReferral(referrerId: String) {
    // Store the referral
    getSharedPreferences("referral", MODE_PRIVATE)
        .edit()
        .putString("pending_referrer", referrerId)
        .apply()

    // Navigate to referral screen
    val intent = Intent(this, ReferralWelcomeActivity::class.java).apply {
        putExtra("referrer_id", referrerId)
    }
    startActivity(intent)
}

Deferred Deep Linking

The hardest case: the user taps the referral link but does not have the app installed. After they install and open the app, the referral should still be attributed.

  1. User taps https://yourdomain.com/referral/user123.
  2. App not installed; user lands on a web page or app store.
  3. The referral context (referrer ID, timestamp, campaign) is stored server-side, associated with a fingerprint (IP, device info, timestamp).
  4. User installs the app and opens it.
  5. On first launch, the app calls a server endpoint with its fingerprint.
  6. Server matches the fingerprint and returns the original referral context.
  7. App processes the referral.

Server-Side Fingerprint Storage

// When the referral link is opened in a browser
app.get('/referral/:referrerId', async (req, res) => {
  const { referrerId } = req.params;

  // Store fingerprint for deferred matching
  const fingerprint = {
    ip: req.ip,
    userAgent: req.headers['user-agent'],
    timestamp: Date.now(),
    referrerId,
    campaign: req.query.campaign
  };

  await storeDeferredLink(fingerprint);

  // Redirect to app store or landing page
  redirectByPlatform(req, res, referrerId);
});

// When the app calls on first launch
app.post('/api/deferred-link', async (req, res) => {
  const { ip, userAgent, deviceId } = req.body;

  const match = await findDeferredLink({
    ip,
    userAgent,
    maxAge: 48 * 60 * 60 * 1000 // 48 hours
  });

  if (match) {
    res.json({
      found: true,
      referrerId: match.referrerId,
      campaign: match.campaign
    });
    // Mark as consumed
    await markDeferredLinkUsed(match.id);
  } else {
    res.json({ found: false });
  }
});

Client-Side (First Launch)

// iOS: check for deferred link on first launch
func checkDeferredLink() {
    guard !UserDefaults.standard.bool(forKey: "deferred_link_checked") else {
        return
    }

    UserDefaults.standard.set(true, forKey: "deferred_link_checked")

    let body: [String: Any] = [
        "deviceId": UIDevice.current.identifierForVendor?.uuidString ?? "",
        "userAgent": "iOS/\(UIDevice.current.systemVersion)"
    ]

    // POST to your server
    apiClient.post("/api/deferred-link", body: body) { result in
        if let referrerId = result.referrerId {
            self.handleReferral(referrerId: referrerId)
        }
    }
}

Attribution Consistency

Referral attribution must be consistent regardless of how the user arrived. Track the referral source for analytics:

interface ReferralAttribution {
  referrerId: string;
  platform: 'ios' | 'android' | 'web';
  method: 'direct' | 'deferred';   // Direct = app was installed; Deferred = installed after
  campaign?: string;
  timestamp: number;
}

function attributeReferral(attribution: ReferralAttribution) {
  // Store locally
  localStorage.setItem('referral', JSON.stringify(attribution));

  // Send to server
  api.post('/api/referral/attribute', attribution);
}

Preventing Double Attribution

A referral should only be attributed once. Guard against duplicate attributions:

async function processReferral(referrerId: string, method: string) {
  // Check if already attributed
  const existing = await getReferralAttribution(userId);
  if (existing) {
    console.log('Referral already attributed:', existing.referrerId);
    return;
  }

  // Store attribution
  await storeReferralAttribution({
    userId,
    referrerId,
    method,
    timestamp: Date.now()
  });

  // Credit the referrer
  await creditReferrer(referrerId);
}

Landing Pages for Web Users

When a desktop user clicks a referral link, they cannot install a mobile app. Show a landing page that:

  1. Explains the referral offer.
  2. Provides app store links (with referral context).
  3. Allows web signup (if applicable).
<!-- referral-landing.html -->
<div class="referral-page">
  <h1>You've been invited!</h1>
  <p>Your friend shared something with you.</p>

  <div class="app-links">
    <a href="https://apps.apple.com/app/idYOUR_ID"
       class="app-store-badge">
      Download on the App Store
    </a>
    <a href="https://play.google.com/store/apps/details?id=com.yourapp"
       class="play-store-badge">
      Get it on Google Play
    </a>
  </div>

  <p>Or <a href="/signup?ref=USER123">sign up on the web</a></p>
</div>

Direct Attribution (App Installed)

# iOS
xcrun simctl openurl booted "https://yourdomain.com/referral/user123?campaign=summer"

# Android
adb shell am start -a android.intent.action.VIEW \
  -d "https://yourdomain.com/referral/user123?campaign=summer" \
  -c android.intent.category.BROWSABLE

Verify:

  • The app opens to the referral screen.
  • The referrer ID user123 is stored.
  • Analytics show method: direct.

Deferred Attribution (App Not Installed)

  1. Open the referral link in a mobile browser.
  2. Verify the user lands on the app store or landing page.
  3. Install the app.
  4. Open the app and verify the referral is attributed.

Web Attribution

  1. Open the referral link on desktop.
  2. Verify the landing page shows.
  3. Click "sign up on web."
  4. Verify the referrer ID is passed to the signup form.

Tolinku provides built-in referral program support with referral links that work across platforms. Tolinku handles deferred deep linking, so referral attribution works even when users install the app after clicking. The platform also provides a referral leaderboard and rewards tracking.

For referral mechanics, see how referral deep links work: end-to-end guide. For the cross-platform deep linking guide, see cross-platform deep linking guide for 2026.

Get deep linking tips in your inbox

One email per week. No spam.

Ready to add deep linking to your app?

Set up Universal Links, App Links, deferred deep linking, and analytics in minutes. Free to start.