Overview

SDK Language Min Version Source Status
iOS (Swift) Swift 5.9+ iOS 15+ deeplink-sdk-ios/ Stable
Android (Kotlin) Kotlin 1.9+ Android 7+ (API 24) deeplink-sdk-android/ Stable
Kotlin Multiplatform Kotlin iOS 15+ / Android 7+ deeplink-sdk-kmp/ Stable
React Native TypeScript RN 0.73+ deeplink-sdk-rn/ Beta
Flutter Dart Flutter 3.16+ deeplink-sdk-flutter/ Beta
Web JS TypeScript / ES2020 Modern browsers deeplink-sdk-web/ Stable
Node.js TypeScript Node 18+ deeplink-sdk-node/ Stable
Python Python 3.10+ deeplink-sdk-python/ Alpha
Go Go 1.21+ deeplink-sdk-go/ Alpha
Unity (C#) C# / Unity 2022+ deeplink-sdk-unity/ Beta

Feature Matrix — what's available where

Quick overview of which features are supported on each SDK. Use this to evaluate before integrating, or to know if you can reach feature parity across platforms.

ℹ️
Légende — ✅ implémenté · ⚠️ partiel ou WIP · ❌ non disponible · ❔ à confirmer (repo non public). Les versions min sont celles testées en CI.
Feature iOS Android KMP RN Flutter Web JS Node Python Go Unity
Setup & Identity
configure() / init✅ 1.0✅ 1.0✅ 0.5✅ 0.9✅ 0.9✅ 1.0✅ 1.0✅ 1.0✅ 0.9⚠️ 0.5
identify(userId)⚠️
setUserProperties()⚠️⚠️
Deep linking
Universal Link / App Link handleOpenURL()n/an/an/an/a⚠️
Deferred matching (post-install)n/an/an/an/a⚠️
A/B test routing (handleOpenURL /ab/)⚠️⚠️⚠️n/an/an/an/a
Server-side buildLink() / shortenn/an/an/an/an/an/an/a
Tracking & events
trackEvent(name, props)⚠️
Batch upload /events/batch⚠️⚠️⚠️
Offline event queue (auto-retry)⚠️⚠️n/an/an/a
Session tracking auto⚠️⚠️n/an/an/a
Custom event schema (validation)
Identity privacy (mobile)
ATT prompt (iOS 14.5+)requestATT()n/an/an/an/an/an/an/an/an/a
IDFA collection (after consent)n/an/an/an/an/an/an/an/an/a
SKAdNetwork (SKAN)n/an/an/an/an/an/an/an/an/a
GAID collectionn/a⚠️⚠️⚠️n/an/an/an/an/a
Install Referrer (Play Store)n/a⚠️⚠️⚠️n/an/an/an/an/a
Web-specific
Smart banner displayn/an/an/an/an/an/an/an/an/a
dataLayer auto-listen (GTM)n/an/an/an/an/an/an/an/an/a
Cross-domain attributionn/an/an/an/an/a⚠️n/an/an/an/a
Server-side / backend ops
Referrals API getReferralCode/claim⚠️
Analytics API getSummaryn/an/an/an/an/an/a⚠️n/a
LTV / Ad Spend APIn/an/an/an/an/an/an/a

Sources de vérité : akeeli-tech/deeplink-sdk-{ios,android,node,python}/README.md publics ; pour KMP / Flutter / RN / Go / Unity, état dérivé de CLAUDE.md workspace + sources récentes. Items ⚠️ sont en backlog STORY-19 / STORY-20 / STORY-21 / STORY-22. Items ❌ Custom event schema en backlog STORY-13. Si une cellule est obsolète, ouvrir une issue dans deeplink-server avec label doc.

The Three Integration Points

For a complete integration you must call all three methods — each covers a different use case:

MethodWhen to CallServer EndpointAnalytics
configure(serverUrl, tenantSlug) Once at app startup
initialize() Once at first launch after install POST /api/v1/match Deferred Installs
handleOpenURL(url) Every URL received — Universal Link, App Link, custom scheme GET /api/v1/resolve/:tenant/:slug App Opens
⚠️
handleOpenURL is essential — when iOS/Android opens the app via Universal Link, the server is never contacted by the OS. Without this call, Universal Link taps are invisible in analytics and you cannot read custom_data.

configure() Parameters

ParameterRequiredDescription
serverUrlYesHTTPS URL of the server, no trailing slash. E.g. "https://links.yourapp.com"
tenantSlugYesYour tenant slug (visible in the dashboard URL). Filters handleOpenURL to your tenant's URLs.
debugLogsNoIf true, logs HTTP requests. Disable in production.

iOS (Swift)

Installation

Swift Package Manager

// Package.swift
.package(
  url: "https://github.com/your-org/deeplink-sdk-ios",
  from: "1.0.0"
)

CocoaPods

# Podfile
pod 'DeepLinkSDK', '~> 1.0'

Full Integration — SwiftUI

import SwiftUI
import DeepLinkSDK

@main
struct MyApp: App {
  init() {
    DeepLinkSDK.shared.configure(
      serverUrl:  "https://links.yourapp.com",
      tenantSlug: "main",
      debugLogs:  true   // disable in production
    )
  }

  var body: some Scene {
    WindowGroup {
      ContentView()
        // (1) Deferred deep link — 1st launch after install
        .task {
          if case .matched(let data) = await DeepLinkSDK.shared.initialize() {
            router.navigate(to: data.customData)
          }
        }
        // (2) Universal Link — Messages, Mail, Notes…
        .onContinueUserActivity(NSUserActivityTypeBrowsingWeb) { activity in
          guard let url = activity.webpageURL else { return }
          Task {
            if case .matched(let data) = await DeepLinkSDK.shared.handleOpenURL(url) {
              await MainActor.run { router.navigate(to: data.customData) }
            }
          }
        }
        // (3) Custom scheme (in-app WebView fallback)
        .onOpenURL { url in
          Task { _ = await DeepLinkSDK.shared.handleOpenURL(url) }
        }
    }
  }
}

Identify User

// Associate a user ID with the current session for better attribution
// and cohort tracking. Without this, users are identified by IP hash,
// which degrades cohort and LTV accuracy.
await DeepLinkSDK.shared.identify(userId: "user_12345")

Track Custom Events

// Simple event
await DeepLinkSDK.shared.trackEvent(name: "signup")

// Event with properties and revenue
// The revenue parameter feeds into LTV/ROAS calculations
await DeepLinkSDK.shared.trackEvent(
  name: "purchase",
  linkSlug: data.slug,   // from initialize() or handleOpenURL()
  properties: ["product_id": "abc"],
  revenue: 29.99,
  currency: "EUR"
)

Offline Queue

Events are queued locally when the device is offline and flushed automatically when connectivity returns. The SDK batches queued events into a single request to POST /api/v1/events/batch.

// No additional code needed — offline queueing is automatic.
// Events tracked while offline are persisted to disk and
// sent in batch when the network becomes available.

Deferred Deep Linking

Deferred deep linking handles the case where the app is not yet installed. The flow is:

  1. User taps a link → server redirects to the App Store / Play Store
  2. User installs the app and opens it for the first time
  3. The SDK calls initialize(), which sends a POST /api/v1/match request with device fingerprint data
  4. The server matches the fingerprint to the original click and returns the link's custom_data
  5. Your app navigates to the intended content
// This is already handled by the initialize() call shown above:
if case .matched(let data) = await DeepLinkSDK.shared.initialize() {
  // Navigate to the content the user originally tapped
  router.navigate(to: data.customData)
}

Consent (GDPR)

// Call before initialize() if consent is required
DeepLinkSDK.shared.setConsent(granted: true)

// To withdraw consent and purge local data
DeepLinkSDK.shared.setConsent(granted: false)
📱
Associated Domains required — In Xcode → Signing & Capabilities, add applinks:links.yourapp.com (no https://, no /*, just the host). Without this, iOS will never recognize the URL as a Universal Link.

Android (Kotlin)

Installation

// build.gradle.kts (app module)
dependencies {
    implementation("com.yourorg:deeplink-sdk-android:1.0.0")
}

Full Integration

// Application.onCreate()
DeepLinkSDK.configure(
    context    = this,
    serverUrl  = "https://links.yourapp.com",
    tenantSlug = "main",
    debugLogs  = BuildConfig.DEBUG
)

// MainActivity.kt
override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    lifecycleScope.launch {
        // (1) Deferred deep link
        (DeepLinkSDK.initialize() as? DeepLinkResult.Matched)?.let {
            navigate(it.data)
        }
    }
    // (2) Cold start via App Link
    intent.data?.let { handleDeepLink(it) }
}

override fun onNewIntent(intent: Intent) {
    super.onNewIntent(intent)
    intent.data?.let { handleDeepLink(it) }
}

private fun handleDeepLink(uri: Uri) = lifecycleScope.launch {
    (DeepLinkSDK.handleOpenUrl(uri) as? DeepLinkResult.Matched)?.let {
        navigate(it.data)
    }
}

Identify User

// Associate a user ID with the current session for better attribution
// and cohort tracking. Without this, users are identified by IP hash,
// which degrades cohort and LTV accuracy.
DeepLinkSDK.identify(userId = "user_12345")

Track Custom Events

// Simple event
DeepLinkSDK.trackEvent(name = "signup")

// Event with properties and revenue
// The revenue parameter feeds into LTV/ROAS calculations
DeepLinkSDK.trackEvent(
    name       = "purchase",
    linkSlug   = data.slug,
    properties = mapOf("product_id" to "abc"),
    revenue    = 29.99,
    currency   = "EUR"
)

Offline Queue

Events are queued locally when the device is offline and flushed automatically when connectivity returns. The SDK batches queued events into a single request to POST /api/v1/events/batch.

// No additional code needed — offline queueing is automatic.
// Events tracked while offline are persisted to disk and
// sent in batch when the network becomes available.

Deferred Deep Linking

Deferred deep linking handles the case where the app is not yet installed. The flow is:

  1. User taps a link → server redirects to the Play Store
  2. User installs the app and opens it for the first time
  3. The SDK calls initialize(), which sends a POST /api/v1/match request with device fingerprint data
  4. The server matches the fingerprint to the original click and returns the link's custom_data
  5. Your app navigates to the intended content
// This is already handled by the initialize() call shown above:
(DeepLinkSDK.initialize() as? DeepLinkResult.Matched)?.let {
    // Navigate to the content the user originally tapped
    navigate(it.data)
}

Kotlin Multiplatform

Installation

// build.gradle.kts (shared module)
val commonMain by getting {
    dependencies {
        implementation("com.yourorg:deeplink-sdk-kmp:1.0.0")
    }
}

Integration

// Shared commonMain code
import com.yourorg.deeplink.DeepLinkSDK

// Configure once at app startup
DeepLinkSDK.configure(
    serverUrl  = "https://links.yourapp.com",
    tenantSlug = "main",
    debugLogs  = true
)

// Deferred deep link — first launch after install
val result = DeepLinkSDK.initialize()
if (result is DeepLinkResult.Matched) {
    navigate(result.data)
}

Identify User

// Associate a user ID with the current session for better attribution
// and cohort tracking. Without this, users are identified by IP hash,
// which degrades cohort and LTV accuracy.
DeepLinkSDK.identify(userId = "user_12345")

Track Custom Events

// Simple event
DeepLinkSDK.trackEvent(name = "signup")

// Event with properties and revenue
// The revenue parameter feeds into LTV/ROAS calculations
DeepLinkSDK.trackEvent(
    name       = "purchase",
    properties = mapOf("product_id" to "abc"),
    revenue    = 29.99,
    currency   = "EUR"
)

Offline Queue

Events are queued locally when the device is offline and flushed automatically when connectivity returns. The SDK batches queued events into a single request to POST /api/v1/events/batch.

// No additional code needed — offline queueing is automatic.
// Events tracked while offline are persisted to platform storage
// and sent in batch when the network becomes available.

Deferred Deep Linking

Deferred deep linking handles the case where the app is not yet installed:

  1. User taps a link → server redirects to the App Store / Play Store
  2. User installs the app and opens it for the first time
  3. The SDK calls initialize(), which sends a POST /api/v1/match request with device fingerprint data
  4. The server matches the fingerprint to the original click and returns the link's custom_data
  5. Your app navigates to the intended content
// This is already handled by the initialize() call shown above:
val result = DeepLinkSDK.initialize()
if (result is DeepLinkResult.Matched) {
    navigate(result.data)
}

React Native

Installation

npm install @deeplink/react-native-sdk
# iOS: pod install

Integration

import { DeepLinkSDK } from '@deeplink/react-native-sdk';
import { Linking } from 'react-native';

// In App.tsx
useEffect(() => {
  DeepLinkSDK.configure({
    serverUrl:  'https://links.yourapp.com',
    tenantSlug: 'main',
  });

  // (1) Deferred deep link
  DeepLinkSDK.initialize().then(result => {
    if (result.matched) navigate(result.data.customData);
  });

  // (2) Universal Link / App Link
  const sub = Linking.addEventListener('url', ({ url }) => {
    DeepLinkSDK.handleOpenURL(url).then(result => {
      if (result.matched) navigate(result.data.customData);
    });
  });

  return () => sub.remove();
}, []);

Identify User

// Associate a user ID with the current session for better attribution
// and cohort tracking. Without this, users are identified by IP hash,
// which degrades cohort and LTV accuracy.
DeepLinkSDK.identify('user_12345');

Track Custom Events

// Simple event
DeepLinkSDK.trackEvent('signup');

// Event with properties and revenue
// The revenue parameter feeds into LTV/ROAS calculations
DeepLinkSDK.trackEvent('purchase', { product_id: 'abc' }, 29.99);

Offline Queue

Events are queued locally when the device is offline and flushed automatically when connectivity returns. The SDK batches queued events into a single request to POST /api/v1/events/batch.

// No additional code needed — offline queueing is automatic.
// The native modules on both iOS and Android persist events
// to disk and send them in batch when the network returns.

Deferred Deep Linking

Deferred deep linking handles the case where the app is not yet installed:

  1. User taps a link → server redirects to the App Store / Play Store
  2. User installs the app and opens it for the first time
  3. The SDK calls initialize(), which sends a POST /api/v1/match request with device fingerprint data
  4. The server matches the fingerprint to the original click and returns the link's custom_data
  5. Your app navigates to the intended content
// This is already handled by the initialize() call shown above:
DeepLinkSDK.initialize().then(result => {
  if (result.matched) navigate(result.data.customData);
});

Flutter

Installation

# pubspec.yaml
dependencies:
  deeplink_sdk: ^1.0.0

Integration

import 'package:deeplink_sdk/deeplink_sdk.dart';

void main() async {
  WidgetsFlutterBinding.ensureInitialized();
  DeepLinkSDK.configure(
    serverUrl:  'https://links.yourapp.com',
    tenantSlug: 'main',
  );
  runApp(const MyApp());
}

class _MyAppState extends State<MyApp> {
  @override
  void initState() {
    super.initState();
    // Deferred deep link
    DeepLinkSDK.initialize().then((result) {
      if (result.matched) navigate(result.data!.customData);
    });
    // Universal / App Links
    DeepLinkSDK.onLink.listen((result) {
      if (result.matched) navigate(result.data!.customData);
    });
  }
}

Identify User

// Associate a user ID with the current session for better attribution
// and cohort tracking. Without this, users are identified by IP hash,
// which degrades cohort and LTV accuracy.
DeepLinkSDK.identify(userId: 'user_12345');

Track Custom Events

// Simple event
DeepLinkSDK.trackEvent('signup');

// Event with properties and revenue
// The revenue parameter feeds into LTV/ROAS calculations
DeepLinkSDK.trackEvent(
  'purchase',
  properties: {'product_id': 'abc'},
  revenue: 29.99,
);

Offline Queue

Events are queued locally when the device is offline and flushed automatically when connectivity returns. The SDK batches queued events into a single request to POST /api/v1/events/batch.

// No additional code needed — offline queueing is automatic.
// Events tracked while offline are persisted to disk and
// sent in batch when the network becomes available.

Deferred Deep Linking

Deferred deep linking handles the case where the app is not yet installed:

  1. User taps a link → server redirects to the App Store / Play Store
  2. User installs the app and opens it for the first time
  3. The SDK calls initialize(), which sends a POST /api/v1/match request with device fingerprint data
  4. The server matches the fingerprint to the original click and returns the link's custom_data
  5. Your app navigates to the intended content
// This is already handled by the initialize() call shown above:
DeepLinkSDK.initialize().then((result) {
  if (result.matched) navigate(result.data!.customData);
});

Web JS

Installation

npm install @deeplink/web-sdk

Integration

import { DeepLinkSDK } from '@deeplink/web-sdk';

const sdk = new DeepLinkSDK({
  serverUrl:  'https://links.yourapp.com',
  tenantSlug: 'main',
});

// Track a page view impression
await sdk.recordImpression({ linkSlug: 'summer-sale' });

// Create a link (requires API key)
const link = await sdk.createLink({
  title: 'Summer Sale',
  webUrl: 'https://example.com/sale',
}, apiKey);

Identify User

// Associate a user ID with the current session for better attribution
// and cohort tracking. Without this, users are identified by IP hash,
// which degrades cohort and LTV accuracy.
await sdk.identify('user_12345');

Track Custom Events

// Simple event
await sdk.trackEvent('signup');

// Event with properties and revenue
// The revenue parameter feeds into LTV/ROAS calculations
await sdk.trackEvent('purchase', { product_id: 'abc' }, 29.99);

Offline Queue

Events are queued in localStorage when the browser is offline and flushed automatically when connectivity returns. The SDK batches queued events into a single request to POST /api/v1/events/batch.

// No additional code needed — offline queueing is automatic.
// The SDK listens for the 'online' event and flushes
// queued events from localStorage in a single batch.

Server-Side SDKs (Node.js / Python / Go)

Node.js

npm install @deeplink/node-sdk
import { DeepLinkClient } from '@deeplink/node-sdk';

const client = new DeepLinkClient({
  serverUrl: 'https://links.yourapp.com',
  apiKey:    process.env.DEEPLINK_API_KEY,
});

const link = await client.links.create({
  title:   'Welcome Email',
  webUrl:  'https://example.com/welcome',
  campaign:'onboarding',
});
console.log(link.shortUrl);

Python

pip install deeplink-sdk
from deeplink_sdk import DeepLinkClient

client = DeepLinkClient(
    server_url="https://links.yourapp.com",
    api_key=os.environ["DEEPLINK_API_KEY"],
)

link = client.links.create(
    title="Welcome Email",
    web_url="https://example.com/welcome",
    campaign="onboarding",
)
print(link.short_url)

Go

go get github.com/your-org/deeplink-sdk-go
import "github.com/your-org/deeplink-sdk-go/deeplink"

client := deeplink.NewClient(deeplink.Config{
    ServerURL: "https://links.yourapp.com",
    APIKey:    os.Getenv("DEEPLINK_API_KEY"),
})

link, err := client.Links.Create(ctx, deeplink.CreateLinkRequest{
    Title:    "Welcome Email",
    WebURL:   "https://example.com/welcome",
    Campaign: "onboarding",
})

Identify User (Server-Side)

Associate a user ID with events for better attribution and cohort tracking. Without this, users are identified by IP hash, which degrades cohort and LTV accuracy.

Node.js

await client.identify('user_12345');

Python

client.identify("user_12345")

Go

client.Identify(ctx, "user_12345")

Track Custom Events (Server-Side)

The revenue parameter feeds into LTV/ROAS calculations.

Node.js

// Simple event
await client.trackEvent('signup');

// Event with properties and revenue
await client.trackEvent('purchase', { product_id: 'abc' }, 29.99);

Python

# Simple event
client.track_event("signup")

# Event with properties and revenue
client.track_event(
    "purchase",
    properties={"product_id": "abc"},
    revenue=29.99,
)

Go

// Simple event
client.TrackEvent(ctx, "signup", nil, 0)

// Event with properties and revenue
client.TrackEvent(ctx, "purchase", map[string]string{
    "product_id": "abc",
}, 29.99)

Unity (C#)

Installation

Import the .unitypackage from the releases page, or add via the Unity Package Manager using the git URL.

Integration

using DeepLink;

public class GameManager : MonoBehaviour
{
    async void Start()
    {
        DeepLinkSDK.Configure(
            serverUrl:  "https://links.yourapp.com",
            tenantSlug: "main",
            debugLogs:  Debug.isDebugBuild
        );

        // Deferred deep link
        var result = await DeepLinkSDK.Initialize();
        if (result.Matched)
        {
            HandleDeepLink(result.Data.CustomData);
        }
    }

    void HandleDeepLink(Dictionary<string, string> customData)
    {
        if (customData.TryGetValue("level", out var level))
            SceneManager.LoadScene(level);
    }
}

Identify User

// Associate a user ID with the current session for better attribution
// and cohort tracking. Without this, users are identified by IP hash,
// which degrades cohort and LTV accuracy.
DeepLinkSDK.Identify("user_12345");

Track Custom Events

// Simple event
DeepLinkSDK.TrackEvent("signup");

// Event with properties and revenue
// The revenue parameter feeds into LTV/ROAS calculations
DeepLinkSDK.TrackEvent(
    "purchase",
    properties: new Dictionary<string, string> { ["product_id"] = "abc" },
    revenue: 29.99f
);

Offline Queue

Events are queued locally when the device is offline and flushed automatically when connectivity returns. The SDK batches queued events into a single request to POST /api/v1/events/batch.

// No additional code needed — offline queueing is automatic.
// Events tracked while offline are persisted to PlayerPrefs and
// sent in batch when the network becomes reachable.

Deferred Deep Linking

Deferred deep linking handles the case where the app is not yet installed:

  1. User taps a link → server redirects to the App Store / Play Store
  2. User installs the app and opens it for the first time
  3. The SDK calls Initialize(), which sends a POST /api/v1/match request with device fingerprint data
  4. The server matches the fingerprint to the original click and returns the link's custom_data
  5. Your app navigates to the intended content
// This is already handled by the Initialize() call shown above:
var result = await DeepLinkSDK.Initialize();
if (result.Matched)
{
    HandleDeepLink(result.Data.CustomData);
}

Custom Data Routing Pattern

custom_data is an arbitrary key-value map stored on each link. Your app decides how to interpret it. Recommended pattern: a screen key plus screen-specific parameters.

// Link created with: custom_data = { "screen": "tab", "tab": "products" }

// iOS Swift router
final class Router: ObservableObject {
  @Published var selectedTab: Tab = .home

  func navigate(to customData: [String: String]) {
    switch customData["screen"] {
    case "tab":
      if let tab = customData["tab"] {
        selectedTab = Tab(rawValue: tab) ?? .home
      }
    case "product":
      if let id = customData["product_id"] {
        navigateToProduct(id: id)
      }
    case "promo":
      if let code = customData["code"] {
        applyPromoCode(code)
      }
    default:
      break
    }
  }
}

Additional Methods

MethodDescription
identify(userId:)Associates a user ID with the current session for better attribution and cohort tracking. Without this call, users are identified by IP hash which degrades cohort/LTV accuracy
forceCheck()Forces initialize() to re-run the /match call (ignores the first-launch flag) — useful for QA/debug
resetFirstLaunch()Resets the first-launch flag — for development testing
setConsent(granted:)Sets GDPR consent. When false, no fingerprint data is sent and local data is purged
trackEvent(name:linkSlug:properties:revenue:currency:)Tracks a custom event attributed to a link