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.

  1. Dashboard

    1. 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).
    2. 2Add a domain (Domains) — or use your *.linktrail.io subdomain. LinkTrail hosts the AASA / assetlinks.json for it automatically.
    3. 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).
    4. 4Grab an API key (Settings → API Keys → Create key, lt_live_…). This is what the SDKs use.
  2. 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")
    PodfileRuby
    pod 'LinkTrailSDK', '~> 0.1.0'

    Android

    app/build.gradle.ktsKotlin
    dependencies {
        implementation("io.linktrail:sdk:0.1.0")
    }

    React Native

    Shell
    npm install linktrail-react-native

    Flutter

    Shell
    flutter pub add linktrail_flutter
    pubspec.yamlYAML
    dependencies:
      linktrail_flutter: ^0.1.0

    Then 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 calls setConsent(true) or setConsent(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.

    Important

    An app that never calls setConsent gets no deferred link and no attribution. Wire both answers of your consent prompt to setConsent, or pass requireConsent: false if consent is collected elsewhere.

    Text
    configure(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 only

    iOS

    App.swiftSwift
    import 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.ktKotlin
    import 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.tsxTypeScript
    import 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.dartDart
    import '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));
  3. Test

    Create a link, tap it from a messages or notes app (not the browser address bar), and watch onLink fire. For deferred: uninstall → tap link → reinstall → first launch → answer your consent prompt → onLink delivers the link. With the default gate, a deferred link does not arrive until setConsent has been called.

    Warning

    Typing 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.

  4. Verify it works

    Nothing above fails loudly, so check these signals in order:

    1. 1SDK logs. Turn them on — iOS: LinkTrailOptions(logEnabled: true, logLevel: .debug), read the Xcode console (lines prefixed [LinkTrail]); Android: LinkTrailOptions(logEnabled = true), then adb logcat -s LinkTrail; React Native and Flutter: pass logEnabled in the options you give configure and read the same native lines. On the first launch expect Install held — awaiting consent decision; after setConsent(true) expect Consent granted, Sending install event… (consent: true), then Install attributed — Install attributed (deterministic click token) when the click token was recovered — or Organic 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 means configure never ran.
    2. 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.
    3. 3Overview and Links. Dashboard → Overview: the installs count goes up. Links → your link shows a click and an install.
    4. 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 through onError as invalidAPIKey (iOS), InvalidApiKey (Android), invalid_api_key (React Native) or LinkTrailInvalidApiKeyException (Flutter) — but only once the install is sent: after setConsent, or at configure with requireConsent: false. Nothing is sent before that.
    5. 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)
Note

Next: the iOS, Android, React Native, and Flutter SDK pages have copy-paste integration for each platform.