Quickstart
Integrate LinkTrail in about five minutes: register an app in the dashboard, add the SDK, and make the four calls that route links and attribute installs.
Two parts: set things up in the dashboard, then wire four calls into your app. Every SDK — iOS, Android, React Native, Flutter — follows the same shape.
Dashboard
- 1Register your app (Apps → Register app). Every app needs a Default fallback web URL. iOS needs the bundle ID, your Apple Team ID and the App Store ID (store routing); Android needs the package name, the Play Store URL and at least one SHA-256 fingerprint (add several — debug, release, Play signing).
- 2Add a domain (Domains) — or use your
*.linktrail.iosubdomain. LinkTrail hosts the AASA /assetlinks.jsonfor it automatically. - 3Create a link (Links → Create link) — choose a domain + slug, a deep link path (the in-app screen, e.g. /products/aj1), your iOS/Android apps and a Fallback URL (required: the web destination when no app is installed); optionally campaign/channel/UTM + custom JSON data. Leave Smart iOS interstitial (recommended) on — it stages the click token that makes iOS deferred links deterministic; switched off, iOS falls back to IP-and-platform matching, and only where that matching is switched on (it is off by default).
- 4Grab an API key (Settings → API Keys → Create key,
lt_live_…). This is what the SDKs use.
App
Add the SDK. One line per platform — the SDK pages below cover the platform-specific setup that follows.
iOS
Package.swiftSwift.package(url: "https://github.com/linktrail-io/ios-sdk.git", from: "0.1.0")PodfileRubypod 'LinkTrailSDK', '~> 0.1.0'Android
app/build.gradle.ktsKotlindependencies { implementation("io.linktrail:sdk:0.1.0") }React Native
Shellnpm install linktrail-react-nativeFlutter
Shellflutter pub add linktrail_flutterpubspec.yamlYAMLdependencies: linktrail_flutter: ^0.1.0Then wire four calls. Every SDK follows the same shape. Consent gating is on by default on every platform (
requireConsent: true) and it holds the install: nothing is sent and the deferred link does not route until your app callssetConsent(true)orsetConsent(false). Grant sends the install counted and attributed; deny sends one uncounted install that only resolves the deferred link, so the user still lands in the right place. Re-engagement links (app already installed) route regardless. The full model is in Consent & privacy.ImportantAn app that never calls
setConsentgets no deferred link and no attribution. Wire both answers of your consent prompt tosetConsent, or passrequireConsent: falseif consent is collected elsewhere.Textconfigure(apiKey) // at launch — re-engagement links route from here; the install waits for setConsent onLink { link, source } // ONE hook for deferred AND normal deep links handleDeepLink(url) // forward incoming OS links to the SDK setConsent(true|false) // after your consent prompt — releases the held install: grant attributes + routes, deny routes onlyiOS
App.swiftSwiftimport LinkTrailSDK // At launch (SwiftUI App.init or AppDelegate). The API key is required — configure throws. // With the default consent gate the install is held here until setConsent (see Consent). try LinkTrail.configure(apiKey: "lt_live_...") // One hook — fires for deferred (first-launch) AND re-engagement links: LinkTrail.shared?.onLink { link, source in router.route(to: link.path, customData: link.customData) } LinkTrail.shared?.onError { error in /* e.g. LinkTrailError.invalidAPIKey */ }Swift// Grant → sends the held install (counted, attributed), routes the deferred link // and flushes the offline event queue: LinkTrail.shared?.setConsent(true) // Deny → sends one uncounted install with no device id; the deferred link still // routes, nothing is recorded, queued events are dropped: LinkTrail.shared?.setConsent(false) // Collecting consent elsewhere (a CMP)? Opt out of the gate — tracking runs from configure, // and an explicit setConsent(false) still wins: try LinkTrail.configure(apiKey: "lt_live_...", options: LinkTrailOptions(requireConsent: false))Android
App.ktKotlinimport io.linktrail.LinkTrail // In Application.onCreate(). The API key is required — a blank key throws. LinkTrail.configure(context = this, apiKey = "lt_live_...") // One hook — fires for deferred (first-launch) AND re-engagement links: LinkTrail.shared?.onLink { link, source -> router.route(link.path, link.customData) } LinkTrail.shared?.onError { error -> /* e.g. LinkTrailError.InvalidApiKey */ }Kotlin// Grant → releases the held install as an attributed install + flushes queued events: LinkTrail.shared?.setConsent(true) // Deny → still releases the install to route the deep link, but records nothing; clears the queue: LinkTrail.shared?.setConsent(false) // To track at init with no gate, opt out (an explicit setConsent(false) still wins): LinkTrail.configure(context = this, apiKey = "lt_live_...", options = LinkTrailOptions(requireConsent = false))React Native
App.tsxTypeScriptimport LinkTrail from 'linktrail-react-native'; // At app launch. The API key is required. The install is sent automatically — // once your app records a consent decision (see Consent below). await LinkTrail.configure('lt_live_...'); // One listener handles first-launch (deferred) AND re-engagement links: LinkTrail.onLink((link, source) => { router.navigate(link.path, link.customData); }); LinkTrail.onError((error) => console.warn('[LinkTrail] ' + error.code + ': ' + error.message));TypeScript// Call once your consent UI resolves — and again on every launch, after configure: LinkTrail.setConsent(true); // grant → install sent + attributed, deferred link routes, queue flushed LinkTrail.setConsent(false); // deny → one uncounted install, deferred link still routes, queue cleared // Collecting consent elsewhere (a CMP)? Opt out of the gate — tracking runs from configure, // and an explicit setConsent(false) still wins: await LinkTrail.configure('lt_live_...', { requireConsent: false });Flutter
main.dartDartimport 'package:flutter/material.dart'; import 'package:linktrail_flutter/linktrail_flutter.dart'; // MyApp and router are your own. Future<void> main() async { WidgetsFlutterBinding.ensureInitialized(); // One stream — fires for deferred (first-launch) AND re-engagement links. // Subscribe BEFORE configure so a cold-start link is not missed: LinkTrail.onLink.listen((event) { router.route(event.link.path, event.link.customData); }); LinkTrail.onError.listen((error) => debugPrint('LinkTrail: $error')); // The API key is required. The install is sent automatically — once your app // records a consent decision (see Consent below). await LinkTrail.configure(apiKey: 'lt_live_...'); runApp(const MyApp()); }Dart// Call once your consent UI resolves — and replay the stored decision on every // launch, right after configure: await LinkTrail.setConsent(true); // grant → install sent + attributed, deferred link routes, queue flushed await LinkTrail.setConsent(false); // deny → one uncounted install, deferred link still routes, queue cleared // Collecting consent elsewhere (a CMP)? Opt out of the gate — tracking runs from configure, // and an explicit setConsent(false) still wins: await LinkTrail.configure(apiKey: 'lt_live_...', options: LinkTrailOptions(requireConsent: false));Test
Create a link, tap it from a messages or notes app (not the browser address bar), and watch
onLinkfire. For deferred: uninstall → tap link → reinstall → first launch → answer your consent prompt →onLinkdelivers the link. With the default gate, a deferred link does not arrive untilsetConsenthas been called.WarningTyping or pasting the link into the browser's address bar never opens the app, on either platform — the OS treats it as browsing, not a link tap. Tap it from another app, or on Android simulate the tap:
adb shell am start -a android.intent.action.VIEW -c android.intent.category.BROWSABLE -d "https://yourapp.linktrail.io/promo". More in Troubleshooting.Verify it works
Nothing above fails loudly, so check these signals in order:
- 1SDK logs. Turn them on — iOS:
LinkTrailOptions(logEnabled: true, logLevel: .debug), read the Xcode console (lines prefixed[LinkTrail]); Android:LinkTrailOptions(logEnabled = true), thenadb logcat -s LinkTrail; React Native and Flutter: passlogEnabledin the options you giveconfigureand read the same native lines. On the first launch expectInstall held — awaiting consent decision; aftersetConsent(true)expectConsent granted,Sending install event… (consent: true), thenInstall attributed—Install attributed (deterministic click token)when the click token was recovered — orOrganic install (no attribution match). Both platforms print[LinkTrail] [LEVEL] message, so these lines read[LinkTrail] [INFO] …on iOS and Android (Android logs them under the LinkTrail tag). No lines at all meansconfigurenever ran. - 2API key Last used. Dashboard → Settings → API Keys: the key's Last used changes from Never after the first call made with a valid key.
- 3Overview and Links. Dashboard → Overview: the installs count goes up. Links → your link shows a click and an install.
- 4onError. A wrong or revoked key does not fail
configure. The backend answers 401 with{"code":401,"message":"Invalid or missing API key"}and the SDK reports it throughonErroras invalidAPIKey (iOS), InvalidApiKey (Android), invalid_api_key (React Native) or LinkTrailInvalidApiKeyException (Flutter) — but only once the install is sent: aftersetConsent, or atconfigurewithrequireConsent: false. Nothing is sent before that. - 5Re-test deferred without reinstalling. Call resetForTesting — LinkTrail.resetForTesting() on iOS, React Native and Flutter, LinkTrail.resetForTesting(context) on Android. It clears the install flag, cached attribution, device ID and the consent decision, so the next launch behaves like a genuine first launch and asks for consent again. Testing only; never ship it.
[LinkTrail] [INFO] Install held — awaiting consent decision [LinkTrail] [INFO] Consent granted [LinkTrail] [INFO] Sending install event… (consent: true) [LinkTrail] [INFO] Install attributed (deterministic click token) [LinkTrail] [INFO] Install attributed [LinkTrail] [INFO] Organic install (no attribution match)- 1SDK logs. Turn them on — iOS:
Next: the iOS, Android, React Native, and Flutter SDK pages have copy-paste integration for each platform.