Deep Links
Branch / Firebase Dynamic Links parity: short links that survive the App Store / Play Store detour and reopen on the article, product, or playlist the user shared from.
Features
- **SDK-side `sendora.links.create()`** — mint links from inside the app with a public key (pk_*); typed `linkData<T>` generic; bundle-id gated server-side.
- **Deferred deep linking (iOS)** — canonical fingerprint match: IP-pinned + 2h window + atomic flip. SDK auto-computes the fingerprint; zero caller-side boilerplate. Cold-install attribution that's better than nothing.
- **Custom JSON `linkData`** delivered to the app on warm + cold open — route via your own business ids (`articleId`, `productId`, ...).
- **Auto-served AASA + assetlinks** per registered iOS / Android app — `/internal/aasa` + `/internal/assetlinks` regenerated on bundle registration.
- **Typed errors** — `LinkError(code, statusCode)` with `BUNDLE_MISMATCH` / `PLAN_LIMIT` / `RATE_LIMITED` / `FALLBACK_REQUIRED` / ... — pattern-match instead of string-sniff.
- **SDK-side `links.revoke()`** for private-content unsend + `links.getStats()` (no dashboard scraping).
- **Click analytics** — geo, device, OS, referrer, deferred-match rate; `/clicks` + `/stats` + `/time-series` endpoints per link.
- **Per-link Open Graph preview overrides** — `ogTitle` + `ogImageUrl` for richer share cards on iMessage / Slack / WhatsApp.
- **Per-platform fallback URLs** — `fallbackUrl` optional; backend defaults from your apps registry (web origin > App Store URL > Play Store URL).
- **Honest non-features:** no `links.prewarm()` background-mint cache (every create is a real HTTPS call); no Android Play Install Referrer integration (iOS fingerprint match is the only deferred path); no custom-branded domains + wildcard SSL (all links served from one canonical Sendora short domain).
Common use cases
- Replace Branch / Firebase Dynamic Links + their CRM-sync plugin + a separate attribution SDK.
- Share-button → article / product / playlist that opens the in-app screen + attributes the share recipient's install.
- Referral + invite flows with deferred attribution AND the referrer auto-credited via audiences + workflow follow-up.
- Email / SMS deep links to specific content with click-through attribution closed back into Analytics.
Key concepts
- Shortcode
- Auto-generated 7-char id (e.g. `aB3xK9p`) that resolves to `iosDeepLinkPath` / `androidDeepLinkPath` + `linkData`.
- Bundle-id gate
- Backend cross-checks the SDK's bundle / package against your registered apps. A leaked `pk_*` can't mint links pointed at someone else's app.
- Deferred match
- User taps a link, installs the app, opens it for the first time — SDK calls `matchDeferred()` and the backend returns the original `linkData` so you can route to the article they came from.
- linkData
- Typed JSON delivered to `onLinkOpened`. Pass a discriminated union for type-safe routing.
Setup
- 1Mint a public API keyDashboard → Settings → API Keys → `pk_prod_*`.
- 2Register your appDashboard → Apps. iOS: bundle id + Team ID. Android: package + SHA-256 signing fingerprint (`./gradlew signingReport`).
- 3AASA + assetlinks auto-served`https://go.sendoracloud.com/.well-known/{apple-app-site-association,assetlinks.json}` populate within seconds.
- 4Wire Associated Domains / App LinksiOS: add `applinks:go.sendoracloud.com` to Signing & Capabilities. Android: add intent-filter with `autoVerify="true"`.
Mint + share a link
Create + prewarm a deep link (Branch / Firebase parity)
FREEMint a short link from inside your mobile app using the public SDK key. Backend auto-generates the shortcode, validates the bundle id against your registered apps, and enforces your plan limit. `fallbackUrl` is optional — backend defaults from your project's apps registry (web origin > iOS App Store URL > Android Play Store URL). Prewarm on row mount so the share tap is instant.
import { SendoraCloud, LinkError } from "@sendoracloud/sdk-web";
const s = SendoraCloud.init({ apiKey: "pk_prod_…" });
interface ArticleLink extends Record<string, unknown> {
type: "article";
articleId: string;
sharedBy: string;
}
// Prewarm on row mount → Share-button click is instant
s.links.prewarm<ArticleLink>(
{
title: `Share: ${article.title}`,
iosDeepLinkPath: `/articles/${article.id}`,
androidDeepLinkPath: `/articles/${article.id}`,
linkData: { type: "article", articleId: article.id, sharedBy: currentUser.id },
},
{ key: `article:${article.id}` },
);
try {
const link = await s.links.create<ArticleLink>(input, { key: `article:${article.id}` });
await navigator.clipboard.writeText(link.url);
// or pass to window.open / navigator.share
} catch (err) {
if (err instanceof LinkError) {
// err.code: "BUNDLE_MISMATCH" | "PLAN_LIMIT" | "RATE_LIMITED" | "FALLBACK_REQUIRED" | ...
}
}Handle a warm-path deep link open
FREECall from your URL handler. On React Native, `attachLinkingApi` is a one-call replacement for the standard `Linking.getInitialURL` + `addEventListener` + extract-pre-check boilerplate — Sendora-shaped URLs flow into the SDK; everything else forwards to your existing router.
// In AppDelegate / SceneDelegate
SendoraCloud.links?.onLinkOpened { event in
do {
let payload: ArticleLink = try event.decodedLinkData()
AppRouter.shared.navigate(toArticle: payload.articleId)
} catch { /* fall through */ }
}
func scene(_ scene: UIScene, continue userActivity: NSUserActivity) {
guard let url = userActivity.webpageURL else { return }
SendoraCloud.links?.handleUniversalLink(url: url)
}Deferred deep link match (cold launch after install)
FREECall once on first foregrounded launch. SDK auto-probes Play Install Referrer on Android (100% accurate) and computes the canonical device fingerprint hash otherwise. Backend pins the fingerprint to the click's IP for a 2h window — no caller-side recipe boilerplate.
// SDK auto-computes the canonical fingerprint when called with no input.
SendoraCloud.links?.matchDeferred { _ in
// onLinkOpened also fires automatically.
}Revoke + stats from the SDK
FREESoft-delete a link (private-content unsend) and pull totals + breakdowns without scraping the dashboard. Idempotent revoke. Both project-scoped via the public API key.
await s.links.revoke("ab3xk9p");
const stats = await s.links.getStats("ab3xk9p");
// → { totalClicks, uniqueClicks, deferredMatches, byOs, byCountry, byDevice }Per-link AASA + assetlinks (auto-served)
FREESendora generates Apple App Site Association + Android assetlinks.json automatically from your registered apps (Dashboard → Apps). Universal Links + App Links work the moment you tap an iOS bundle id or Android SHA-256 fingerprint into the dashboard.
# https://go.sendoracloud.com/.well-known/apple-app-site-association
# https://go.sendoracloud.com/.well-known/assetlinks.json
# Both are computed from your project's apps + active links — no manual upload.Production gotchas
- Deferred match has a 2h window from click. Mobile install latency rarely exceeds minutes; tighter window reduces brute-force surface.
- Bundle-id gate is **fail-closed** — fresh projects with zero registered apps refuse `links.create()` from the SDK. Register your app before going to prod.
- Android Play Install Referrer is 100% accurate; the iOS fingerprint fallback is probabilistic (~80–90%). Always wire the referrer path on Android.
- Use typed `linkData<T>` with a discriminated union — `(linkData.articleId as string | undefined)` casts age badly.
- `fallbackUrl` is optional from the SDK as of API 1.8.0 — backend defaults from your apps registry (web origin > iOS App Store URL > Android Play Store URL).