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:
https://YOUR_APP_ID.mobana.ai/deeplink
?data={"unlock":"PROMO50","ref":"creator123"}
&utm_source=tiktok
&utm_campaign=influencer_dropWhen the creator's audience taps it:
- App installed: the OS opens your app directly (Universal Link / App Link). Your
onDeepLinkhandler 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
onDeepLinkhandler fires withsource: '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
- 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).
- Find the Team ID field. It is a 10-character alphanumeric string — e.g.
ABCDE12345. - Paste it into Dashboard → App Settings → Deeplinks → iOS Team 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_aliasOption B — Android Studio
- Open your project in Android Studio.
- Open the Gradle panel (View → Tool Windows → Gradle) and run
signingReportunder Tasks → android. - 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:
- In the Google Play Console, go to your app → Release → Setup → App integrity.
- Copy the SHA-256 certificate fingerprint shown under App signing key certificate.
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.
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.comAndroid — 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>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.
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:
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.
| Installed | Attributed | UL fires | getAttribution() returns | onDeepLink fires |
|---|---|---|---|---|
| Yes | Yes | Yes | (cached, unchanged) | universal_link |
| Yes | Yes | No | (cached, unchanged) | probabilistic (probe) |
| Yes | No | Yes | matched on first launch | universal_link |
| Yes | No | No | matched on first launch | deferred |
| No | — | — | matched on first launch | deferred |
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
installIdis sent on probe / record calls. - Server doesn't write a per-device
DeeplinkClickrow. The probe still claims the matchedClickso it can't be re-fired, but no row links it to a device. /deeplink/recordis not called at all when a Universal Link fires — the SDK parses the URL locally and surfaces it viaonDeepLinkonly.
Security notes#
- Don't put secrets in the URL. The
dataparam 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
onDeepLinkon 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).