Smart Links & Deeplinking

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.

What is a smart link?#

A smart link is a URL on your {appId}.mobana.ai domain that delivers a structured payload (unlocks, referral codes, in-app routing, etc.) to your app — regardless of whether the user has the app installed and regardless of which browser they tapped from.

Use one when you want any of:

  • Influencer / referral links that unlock something in-app
  • Push, email, or share links that deep-link past the launch screen
  • QR codes that route returning users to a specific feature
  • Re-engagement campaigns where the URL itself is the payload

Use /redirect (instead of /deeplink) for traditional ad-attribution campaigns where the web flow itself adds value (landing pages, web pixels, social-proof previews). Both live side by side; pick per link.

A concrete example#

You DM a creator their unique link:

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

When the creator's audience taps it:

  • App installed: the OS opens your app directly (Universal Link / App Link). Your onDeepLink handler fires within ~50 ms with the parsed payload.
  • App not installed: the user goes through the App Store / Play Store. On first launch, the SDK matches the click probabilistically and your onDeepLink handler fires with source: 'deferred' — same payload.
  • App installed but UL failed (in-app browsers like Instagram / TikTok strip Universal Links): the user lands in the App Store, taps Open, your app launches, the SDK probes the server, and your handler fires with source: 'probabilistic'.

Setup#

1. Configure your app for Universal Links / App Links

In Dashboard → App Settings → Deeplinks, add your iOS Team ID and your Android SHA-256 fingerprint(s). Mobana serves the apple-app-site-association and assetlinks.json files automatically at https://{appId}.mobana.ai/.well-known/... — you don't host them yourself.

Finding your iOS Team ID

  1. Sign in to App Store Connect and navigate to Users and Access → Integrations (or open the Apple Developer portal and go to Account → Membership Details).
  2. Find the Team ID field. It is a 10-character alphanumeric string — e.g. ABCDE12345.
  3. Paste it into Dashboard → App Settings → Deeplinks → iOS Team ID.
Combined with your Bundle ID

Mobana combines the Team ID with your iOS Bundle ID (from App Details) to form the appIDs entry in the AASA file: TEAMID.com.yourcompany.yourapp. Make sure the Bundle ID in App Settings matches the one in your Xcode project exactly.

Finding your Android SHA-256 fingerprint(s)

You need one fingerprint per signing key (debug key, release key, and — if you use Play App Signing — the App Signing certificate). Add all of them so smart links work during development and in production.

Option A — keytool (local keystore)

Run this for each keystore file. The SHA-256 line under Certificate fingerprints is what you need.

keytool -list -v -keystore /path/to/your.keystore -alias your_key_alias

Option B — Android Studio

  1. Open your project in Android Studio.
  2. Open the Gradle panel (View → Tool Windows → Gradle) and run signingReport under Tasks → android.
  3. Look for the SHA-256 value in the output for the relevant variant.

Option C — Play App Signing certificate

If you use Google Play App Signing (the default since 2021), Google re-signs your APK with their key. You must also add their certificate fingerprint:

  1. In the Google Play Console, go to your app → Release → Setup → App integrity.
  2. Copy the SHA-256 certificate fingerprint shown under App signing key certificate.
Include all signing keys

Missing a fingerprint means App Links won't verify for that build variant. Debug builds fail silently — the Android OS falls back to opening the browser instead of your app. Add both your debug and release fingerprints while developing.

Fill these in before shipping

Apple's CDN caches the AASA file for up to 7 days. If Apple fetches it before you've entered your Team ID, the empty version is locked in and smart links degrade to the web flow for up to a week. Add your credentials in the dashboard before submitting a build that includes the applinks: entitlement or the Android intent-filter.

2. Register the domain in your mobile app

iOS — Xcode → Signing & Capabilities → Associated Domains:

applinks:YOUR_APP_ID.mobana.ai
# Or, when using a custom endpoint:
applinks:yourdomain.com

Android — AndroidManifest.xml:

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

<!-- Mobana subdomain (always required) -->
<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="/deeplink" />
</intent-filter>

<!-- Custom domain — add this block only if you use a custom endpoint.
     Each host needs its own <intent-filter>; mixing hosts in one filter
     creates unintended host × path combinations. -->
<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/deeplink" />
</intent-filter>
Always include android:pathPrefix

Without android:pathPrefix="/deeplink", Android verifies your app for the entire host — meaning taps on /redirect, /find, and other Mobana paths could also open your app instead of the browser. The pathPrefix scopes App Link handling to smart-link URLs only, matching what Mobana's iOS AASA file already does on the Apple side.

If you use a custom endpoint, add a second <intent-filter> for your domain (see the snippet above) with the path prefix matching how your proxy forwards smart links — typically /da/deeplink. Do not combine two hosts in one filter: Android creates the cartesian product of all <data> elements, which produces unintended host + path combinations.

Custom endpoints

Using a custom domain (e.g. yourdomain.com/da)? Your reverse proxy must forward both /.well-known/apple-app-site-association and /.well-known/assetlinks.json to Mobana. The auto-generated proxy templates in App Settings → Custom Endpoint already include these. See the Custom Endpoints guide for details.

3. Wire your payload handler

Wire the same idempotent payload handler to both getAttribution() and onDeepLink() — different delivery cases surface via different paths, and a few cases fire both:

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

await Mobana.init({
  appId: 'a1b2c3d4',
  appKey: process.env.MOBANA_APP_KEY,
});

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

// Wire the SAME idempotent handler to both surfaces:
// - getAttribution() — surfaces deferred deeplinks via the install attribution path.
// - onDeepLink()     — surfaces every smart-link tap (UL, deferred, probe).
function applyPayload(data?: UnlockData, ref?: string) {
  if (data?.unlock) unlockPromo(data.unlock);
  if (data?.ref || ref) trackReferral(data?.ref ?? ref);
}

const result = await Mobana.getAttribution<UnlockData>();
if (result.status === 'matched') {
  applyPayload(result.attribution.data, result.attribution.utm_source);
}

Mobana.onDeepLink<UnlockData>((event) => {
  applyPayload(event.data, event.utm.source);
});

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>

Delivery matrix#

The following table shows what fires for each combination of install state / attribution state / Universal-Link interception. The takeaway: wire one idempotent handler to both surfaces and you don't need to special-case any cell.

InstalledAttributedUL firesgetAttribution() returnsonDeepLink fires
YesYesYes(cached, unchanged)universal_link
YesYesNo(cached, unchanged)probabilistic (probe)
YesNoYesmatched on first launchuniversal_link
YesNoNomatched on first launchdeferred
Nomatched on first launchdeferred

Privacy / tracking-disabled mode#

When you call Mobana.setTrackingEnabled(false) (or pass enableTracking: false to init()), smart links still deliver payloads — your in-app behaviour (unlocks, ref tracking, routing) keeps working in all five 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.
  • /deeplink/record is not called at all when a Universal Link 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.
  • 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 as usual.
  • 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