Deeplinks

One Mobana URL that opens your app directly when installed, falls through to install + deferred deeplink when not, and recovers from in-app browser failures via a re-engagement probe.

Two link types#

Mobana links come in two flavours, each a URL on your {appId}.mobana.ai host. Choose the path based on your intent:

  • /link — Acquisition. Captures attribution and redirects straight to the App Store or Play Store. No app-open attempt, no timeout delay. Best for ad campaigns and new-user acquisition where a clean, uninterrupted store funnel matters.
  • /deep — Deeplink. Registered as a Universal Link / App Link so the OS opens the installed app instantly. When the app is not installed, Mobana captures attribution, fires a scheme:// / intent:// app-open attempt, and falls back to the store after 2–3 s. Best for re-engagement — unlock codes, referral links, push notifications, share flows.

Both paths support the same payload parameters (data, UTM fields, custom params) and write an attribution Click. Your onDeepLink handler fires for both.

How direct opening works#

When a /deep link is tapped and your app is already installed, the OS intercepts it via Universal Links / App Links (see Universal Links / App Links below) and opens the app directly — no browser involved. If UL is not set up, the link falls through to the browser and Mobana fires your configured URL scheme to open the app:

  • iOS — fires myapp://abc123.mobana.ai/deep?…. If the app is installed, iOS switches to it immediately. The SDK receives the URL via Linking, parses the payload, and fires onDeepLink.
  • Android — fires an intent:// URL with your package name. Chrome opens your app directly. The SDK receives the URL and fires onDeepLink.

If the app is not installed, the user goes through the App Store / Play Store. On first launch the SDK calls /find to match the install to the recorded Click and fires onDeepLink with source: 'deferred' — same payload.

A concrete example#

You DM a creator their unique link:

Mobana link
https://YOUR_APP_ID.mobana.ai/link
  ?data={"unlock":"PROMO50","ref":"creator123"}
  &utm_source=tiktok
  &utm_campaign=influencer_drop

When the creator's audience taps it:

  • App installed: Mobana fires myapp:// (iOS) or intent:// (Android) to open your app directly. Your onDeepLink handler fires with the parsed payload.
  • App not installed: the user goes through the App Store / Play Store. On first launch the SDK matches the click and fires onDeepLink with source: 'deferred' — same payload.
  • In-app browsers (Instagram, TikTok, etc.): scheme-based opening usually still works. If the app isn't installed, the browser redirects to the store, and the SDK probes on next launch with source: 'probabilistic'.

Setup#

1. Configure your app in the Dashboard

In Dashboard → App Settings → Deeplinks, add:

  • iOS URL Scheme — the custom URL scheme registered in your app's Info.plist (e.g. myapp). Mobana fires myapp:// from the redirect page to open your app when it's installed.
  • iOS Team ID (optional) — enables Universal Links so iOS can intercept the URL before opening the browser at all. See Universal Links / App Links.
  • Android SHA-256 fingerprints (optional) — enables Android App Links for the same OS-level intercept on Android.
Android package name

Android intent:// opening uses your app's Package Name, which you already configured in App Details when adding your Android platform. No additional dashboard field needed.

2. Register the iOS URL Scheme in your app

Bare React Native — add to ios/YourApp/Info.plist:

Info.plist
<!-- ios/YourApp/Info.plist -->
<key>CFBundleURLTypes</key>
<array>
  <dict>
    <key>CFBundleURLSchemes</key>
    <array>
      <string>myapp</string>
    </array>
  </dict>
</array>

Expo — add scheme to app.json:

app.json
{
  "expo": {
    "scheme": "myapp"
  }
}
The scheme must match exactly

The value in Info.plist / app.json must exactly match the iOS URL Scheme you entered in the Dashboard (case insensitive). If they differ, iOS won't open your app when the scheme URL fires.

3. Add an Android intent-filter

Add an <intent-filter> to your launcher activity in AndroidManifest.xml. This lets the Android Linking API receive the URL from Mobana's intent:// redirect:

<!-- AndroidManifest.xml — inside the launcher <activity> -->

<!-- Required for intent:// deep link routing. The pathPrefix scopes this
     filter to Mobana links only — without it, any https:// link on your
     Mobana host could open the app. autoVerify enables App Links once you
     add SHA-256 fingerprints in App Settings → Deeplinks -->
<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="YOUR_APP_ID.mobana.ai"
          android:pathPrefix="/deep" />
<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="/da/deep" />
</intent-filter>
Always include android:pathPrefix

Without android:pathPrefix="/deep", Android may route taps on /find, /conversion, and other Mobana paths through your app instead of the browser. The pathPrefix scopes this filter to /deep links only.

4. Wire your payload handler

Use onDeepLink() as the single surface for all routing actions — unlocks, referral codes, in-app navigation. It fires for every delivery case: scheme open, deferred (first launch after install), and probe (re-engagement). You don't need to wire getAttribution() separately for routing — that's for install analytics only (campaign data, ad network click IDs).

App.tsx
import { Mobana } from '@mobana/react-native-sdk';

interface UnlockData { unlock?: string; ref?: string }

// onDeepLink fires for every Mobana link tap — scheme open, deferred
// (first launch after install), and re-engagement probe. Branch on the
// payload: act on what you care about, ignore the rest as a vanilla
// campaign click.
Mobana.onDeepLink<UnlockData>((event) => {
  if (event.data?.unlock) unlockPromo(event.data.unlock);
  if (event.data?.ref) trackReferral(event.data.ref);
});

If you already use <MobanaProvider> for flows, you can add onDeepLink directly to it:

<MobanaProvider
  onDeepLink={(event) => {
    if (event.data?.unlock) unlockPromo(event.data.unlock);
  }}
>
  <App />
</MobanaProvider>

Expo Router#

This step is only needed if your app uses Expo Router (file-based routing). Bare React Native and React Navigation work out of the box — skip ahead.

Expo Router evaluates every incoming URL against your routes and renders an "Unmatched Route" screen for anything it can't match. Mobana links (https://abc123.mobana.ai/link?… and the myscheme:// opens) aren't app routes — the SDK consumes them via Linking — so Expo Router shows that error screen before you ever see the deeplink.

Expo Router provides a pre-routing hook, app/+native-intent.tsx, that filters URLs before they hit the router. The SDK ships a ready-made filter — drop it in and Mobana URLs are skipped while everything else routes normally:

app/+native-intent.tsx
// app/+native-intent.tsx
// Default endpoint ({appId}.mobana.ai), or a custom endpoint set via
// EXPO_PUBLIC_MOBANA_ENDPOINT — just re-export, no setup needed.
export { redirectSystemPath } from '@mobana/react-native-sdk/expo-router';

Using a custom endpoint? Either set EXPO_PUBLIC_MOBANA_ENDPOINT (the re-export above picks it up automatically), or build the filter explicitly with the same endpoint you give Mobana.init():

app/+native-intent.tsx
// app/+native-intent.tsx
import { createRedirectSystemPath } from '@mobana/react-native-sdk/expo-router';

// Custom endpoint — pass the same endpoint you give Mobana.init().
export const redirectSystemPath = createRedirectSystemPath({
  endpoint: 'https://go.yourapp.com/da',
});
Already have a +native-intent.tsx?

If you handle other links there too, compose the filter — run your own rules first, then hand the rest to Mobana:

app/+native-intent.tsx
// app/+native-intent.tsx
import { createRedirectSystemPath } from '@mobana/react-native-sdk/expo-router';

const filterMobana = createRedirectSystemPath();

export function redirectSystemPath({
  path,
  initial,
}: {
  path: string;
  initial: boolean;
}) {
  // your own pre-routing rules here, then hand the rest to Mobana
  return filterMobana({ path, initial });
}
Attribution still works without it

The SDK receives deeplinks via Linking regardless of this file, so attribution and onDeepLink keep firing. The filter's job is purely to suppress Expo Router's "Unmatched Route" screen for a clean user experience.

Delivery matrix#

What fires for each combination of install state, attribution state, and direct-open success. The takeaway: onDeepLink fires in every cell — wire your routing logic there and you don't need to special-case any delivery path.

InstalledAttributedApp opens viagetAttribution() returnsonDeepLink fires
YesYesscheme / UL(cached, unchanged)universal_link
YesYesscheme fails → store(cached, unchanged)probabilistic (probe)
YesNoscheme / ULmatched on first launchuniversal_link
YesNoscheme fails → storematched on first launchdeferred
NoApp Store / Play Storematched on first launchdeferred

onDeepLink vs getAttribution()#

onDeepLink is the right place for all routing actions — unlocks, referral codes, screen navigation. It fires for every delivery case (scheme open, deferred, probe), and the same payload arrives every time.

getAttribution() is for install attribution analytics: reading which campaign or channel brought the user, and reporting click IDs (ttclid, fbclid, gclid, etc.) back to ad networks. It returns whichever Click ended up matching this install regardless of whether the user tapped a plain campaign link or a payload-carrying one.

// getAttribution() is for install analytics — read the campaign + click IDs
// that brought the user and report them to your ad networks.
const result = await Mobana.getAttribution();
if (result.status === 'matched') {
  analytics.track('install_attributed', {
    utm_source: result.attribution?.utm_source,
    utm_campaign: result.attribution?.utm_campaign,
  });
  if (result.attribution?.click_params?.ttclid) {
    reportToTikTok({ ttclid: result.attribution.click_params.ttclid });
  }
}

Don't mirror onDeepLink routing logic in your getAttribution() path — that would double-execute on first launch (deferred deeplink also resolves via onDeepLink).

Privacy / tracking-disabled mode#

When you call Mobana.setTrackingEnabled(false) (or pass enableTracking: false to init()), Mobana links still deliver payloads — your in-app behaviour keeps working in all cases above. What changes server-side:

  • No installId is sent on probe / record calls.
  • Server doesn't write a per-device DeeplinkClick row. The probe still claims the matched Click so it can't be re-fired, but no row links it to a device.
  • /link/record is not called at all when a scheme URL fires — the SDK parses the URL locally and surfaces it via onDeepLink only.

Security notes#

  • Don't put secrets in the URL. The data param is visible to anyone who gets the link. Treat it like a signed coupon code, not a JWT.
  • Custom URL schemes can be registered by any app. If your scheme name is generic (e.g. app), another installed app could intercept it. Use a unique scheme like com.yourcompany.yourapp or your brand name. Universal Links (when configured) are cryptographically verified and immune to hijacking.
  • Re-engagement window is intentionally short (5 min). A click can only trigger an onDeepLink on an installed device for 5 minutes after the tap. For deferred installs, the longer install-attribution window applies.
  • Probe matches require a tight signal match. Confidence ≥ 0.9 (vs 0.7 for install attribution). If you see false negatives, check that your custom-endpoint proxy is forwarding the real client IP (X-Real-IP).
AI agents: for complete Mobana SDK & API documentation, get full context here or visit llms.txt