Linkzly

Android SDK Setup Guide (Kotlin)

The Linkzly Android SDK is a full mobile attribution and deep linking platform. It tracks installs, opens, and custom events; handles standard URL schemes, App

9 min read

Android SDK Setup Guide (Kotlin)

The Linkzly Android SDK is a full mobile attribution and deep linking platform. It tracks installs, opens, and custom events; handles standard URL schemes, App Links, and deferred deep linking; captures affiliate attribution from incoming links; integrates Firebase Cloud Messaging for push; and includes a dedicated gaming event pipeline.

The SDK is written in Kotlin, fully compatible with Java, lifecycle-aware, ProGuard/R8 ready, and ships at under 100 KB.


Prerequisites

Before integrating, set up your app in the Linkzly Console:

  1. Go to Dashboard > Apps and click "Register App".
  2. Enter your Android Package Name and SHA-256 certificate fingerprints.
  3. Choose a verification method (Hosted is recommended for a quick start).
  4. Copy your SDK Key from the post-creation wizard, or from Manage App > Overview > SDK Configuration.
Requirement Minimum Recommended
Android SDK API 21 (5.0 Lollipop) API 33+
Target SDK API 34 API 34+
Java 8+ 11+
Kotlin 1.8+ 1.9+
Gradle 7.0+ 8.0+

Your SDK key starts with slk_ and uniquely identifies one app — do not share keys between apps.


Installation

Step 1 — Add the JitPack repository

In settings.gradle.kts (or root build.gradle for older setups):

dependencyResolutionManagement {
    repositories {
        google()
        mavenCentral()
        maven { url = uri("https://jitpack.io") }
    }
}

Step 2 — Add the dependency

In your app-level build.gradle:

dependencies {
    implementation 'com.github.Linkzly:linkzly-android-sdk:1.0.1'
}

Sync Gradle. The SDK transitively pulls in OkHttp and kotlinx.serialization. INTERNET and ACCESS_NETWORK_STATE permissions are merged in automatically.


Platform Setup

AndroidManifest.xml

Register a custom Application class and add intent filters to your launcher Activity for custom URL schemes and App Links:

<application
    android:name=".MyApplication"
    ... >

    <activity
        android:name=".MainActivity"
        android:launchMode="singleTask"
        android:exported="true">

        <intent-filter>
            <action android:name="android.intent.action.MAIN" />
            <category android:name="android.intent.category.LAUNCHER" />
        </intent-filter>

        <!-- Custom URL scheme (for testing) -->
        <intent-filter>
            <action android:name="android.intent.action.VIEW" />
            <category android:name="android.intent.category.DEFAULT" />
            <category android:name="android.intent.category.BROWSABLE" />
            <data android:scheme="yourapp" />
        </intent-filter>

        <!-- App Links (production) -->
        <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" />
        </intent-filter>
    </activity>
</application>

android:launchMode="singleTask" is required so warm-start deep links route through onNewIntent().

App Links verification

If you selected Hosted Verification in the console, Linkzly hosts your assetlinks.json automatically — point the App Links intent filter at applinks:{your-prefix}.linkz.ly and you are done. For Custom Domain Verification, host the file at https://yourdomain.com/.well-known/assetlinks.json containing your package name and SHA-256 fingerprint. The android:autoVerify="true" attribute triggers verification at install time.

Verify on a device with:

adb shell pm get-app-links com.yourcompany.yourapp
adb shell pm verify-app-links --re-verify com.yourcompany.yourapp

Initialization

Call LinkzlySDK.configure() once in Application.onCreate():

import android.app.Application
import com.linkzly.sdk.LinkzlySDK
import com.linkzly.sdk.models.Environment

class MyApplication : Application() {
    override fun onCreate() {
        super.onCreate()

        LinkzlySDK.configure(
            context = this,
            sdkKey = "slk_your_key_from_console",
            environment = Environment.PRODUCTION  // or STAGING
        )
    }
}

The SDK observes ProcessLifecycleOwner and handles install tracking on first launch, open tracking on subsequent launches, session start/end on foreground/background transitions, and event-queue flushes when the app backgrounds.


Tracking Events

// Custom event
LinkzlySDK.trackEvent("purchase_completed", mapOf(
    "product_id" to "abc123",
    "price" to 29.99,
    "currency" to "USD"
))

// Purchase event with callback
LinkzlySDK.trackPurchase(
    parameters = mapOf("amount" to 9.99, "currency" to "USD", "sku" to "premium_monthly"),
    callback = { result -> result.onSuccess { /* tracked */ } }
)

// Batch
LinkzlySDK.trackEventBatch(listOf(event1, event2, event3)) { /* result */ }

// Manual install / open with deferred-deep-link callback
LinkzlySDK.trackInstall { result ->
    result.onSuccess { deepLinkData -> deepLinkData?.let { navigateTo(it.path) } }
}
LinkzlySDK.trackOpen { result -> /* ... */ }

trackInstall and trackOpen are called automatically by the lifecycle observer; use the explicit form when you need the deferred deep-link callback (e.g., on first launch from a Linkzly install link).

Event queue

Events are queued locally and flushed in batches. For manual control:

LinkzlySDK.flushEvents { result -> /* ... */ }
val pending: Int = LinkzlySDK.getPendingEventCount()
val queued: List<QueuedEvent> = LinkzlySDK.getPendingEvents()

The queue holds up to 10,000 events and is persisted across app restarts.


User Identity

LinkzlySDK.setUserID("user_12345")          // after login
val userId: String? = LinkzlySDK.getUserID()
LinkzlySDK.setUserID("")                     // clear after logout

// Persistent visitor ID (auto-generated UUID)
val visitorId: String = LinkzlySDK.getVisitorID()
LinkzlySDK.resetVisitorID()                  // generate a new one

There is no separate clearUserID() method — pass an empty string.


Deep Linking and App Links

Parse incoming intents in your Activity:

class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        LinkzlySDK.handleAppLink(intent)?.let { route(it) }
    }

    override fun onNewIntent(intent: Intent?) {
        super.onNewIntent(intent)
        intent?.let { LinkzlySDK.handleAppLink(it)?.let(::route) }
    }

    private fun route(data: DeepLinkData) {
        when (data.path) {
            "/product" -> showProduct(data.getStringParameter("id"))
            "/promo"   -> applyPromo(data.getStringParameter("code"))
        }
    }
}

DeepLinkData exposes type-safe getters:

data.url                          // full URL string
data.path                         // URL path
data.smartLinkId                  // Linkzly smart link ID
data.clickId                      // attribution click ID
data.getStringParameter("name")   // String?
data.getIntParameter("count")     // Int?
data.getDoubleParameter("price")  // Double?
data.getBooleanParameter("flag")  // Boolean (defaults to false)

Affiliate Attribution

Capture an affiliate click from any incoming deep link, then read it back at checkout for server-to-server (S2S) reporting:

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    LinkzlySDK.captureAffiliateAttribution(intent.data)
}

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

At conversion time:

if (LinkzlySDK.hasAffiliateAttribution()) {
    val attribution: AffiliateAttribution = LinkzlySDK.getAffiliateAttribution()
    // attribution.clickId, programId, affiliateId, timestamp, source
    val clickId = LinkzlySDK.getAffiliateClickId()
    yourApi.reportConversion(clickId, orderId, revenue = 29.99)
}

LinkzlySDK.clearAffiliateAttribution()

Attribution is stored in SharedPreferences, expires automatically after 30 days, and is overwritten by newer affiliate clicks.


Push Notifications

The SDK can subscribe the device to a Linkzly Firebase Cloud Messaging broadcast topic — independent of any other push provider you use. Prerequisites:

  • Firebase Cloud Messaging integrated in your app
  • A valid google-services.json in your app module
val success = LinkzlySDK.initializePush()   // subscribe
LinkzlySDK.disablePush()                     // unsubscribe

initializePush() uses runtime reflection — there is no Firebase dependency in the SDK itself, so it returns false safely when Firebase is unavailable. If you use Huawei Push Kit or another non-FCM provider, handle token registration through that provider and pass attribution data through custom events instead.


Privacy Controls

LinkzlySDK.setTrackingEnabled(false)
LinkzlySDK.isTrackingEnabled()

LinkzlySDK.setAdvertisingTrackingEnabled(false)  // disables GAID collection only
LinkzlySDK.isAdvertisingTrackingEnabled()

GDPR opt-out:

fun handleGDPRDeletion() {
    LinkzlySDK.setTrackingEnabled(false)
    LinkzlySDK.setAdvertisingTrackingEnabled(false)
    LinkzlySDK.setUserID("")
    LinkzlySDK.resetVisitorID()
    LinkzlySDK.clearAffiliateAttribution()
}

The SDK is GDPR- and CCPA-compliant. No PII is collected unless you set it via setUserID(); location, contacts, SMS, browsing history, and media are never collected.


Gaming Tracking

A separate event pipeline for game telemetry with automatic batching, retry logic, optional HMAC request signing, and offline support.

Configure

LinkzlySDK.configureGamingTracking(
    context = this,
    options = LinkzlyGamingTracking.GamingOptions(
        apiKey         = "YOUR_GAMING_API_KEY",
        organizationId = "YOUR_ORG_ID",
        gameId         = "YOUR_GAME_ID",
        gameVersion    = BuildConfig.VERSION_NAME,
        debug          = BuildConfig.DEBUG,
        signingSecret  = "YOUR_SIGNING_SECRET" // required when HMAC is enabled in Game Settings
    )
)

Identify player and track events

LinkzlySDK.identifyGamingPlayer(
    playerId = "player_abc123",
    traits   = mapOf("level" to 42, "vip_tier" to "gold")
)

LinkzlySDK.trackGamingEvent("level_complete", mapOf(
    "level" to 15, "score" to 48000, "stars" to 3
))

// Bypass the batch queue for critical events
LinkzlySDK.trackGamingEventImmediate("purchase", mapOf(
    "item" to "gem_pack_100", "price" to 4.99, "currency" to "USD"
))

Sessions and queue

LinkzlySDK.startGamingSession()         // only needed when autoSessionTracking = false
LinkzlySDK.endGamingSession()

LinkzlySDK.flushGamingEvents { result -> /* ... */ }
val count: Int = LinkzlySDK.getGamingPendingEventCount()
LinkzlySDK.hasGamingInflightBatch()
LinkzlySDK.resetGamingTracking()        // clear queue + player ID + attribution

Gaming attribution

LinkzlySDK.setGamingAttribution(
    clickId          = "click_abc123",
    deferredDeepLink = "yourapp://game/promo?bonus=100",
    metadata         = mapOf("campaign" to "summer_sale")
)
LinkzlySDK.clearGamingAttribution()

HMAC request signing

HMAC signing is enabled by default for new games. Supply signingSecret in GamingOptions — the SDK computes the signature and attaches the X-Signature-256, X-Timestamp, and X-Nonce headers to every batch. The replay window is fixed at 300 seconds, so device clocks must be within 5 minutes of UTC. Your signing secret is in Game Settings → SDK Configuration, separate from your SDK Key. Never commit it to source — use Gradle secrets or a build-time injection mechanism.

GamingOptions reference

Option Default Notes
apiKey, organizationId, gameId required
baseUrl https://gaming.linkzly.com Override only for a custom deployment
maxBatchSize 100
maxBatchBytes 524288 (512 KB)
flushIntervalMs 5000
maxRetries 3
retryDelayMs 1000
maxQueueSize 10000
sessionTimeoutMs 1800000 (30 min)
autoSessionTracking true
signingSecret null Required when HMAC is enabled
debug false Verbose logs under LinkzlyGaming tag

ProGuard / R8

The SDK ships with consumer ProGuard rules — no manual configuration required. If you hit issues, add:

-keep public class com.linkzly.sdk.LinkzlySDK { *; }
-keep public class com.linkzly.sdk.models.** { *; }
-keep public class com.linkzly.sdk.gaming.** { *; }
-dontwarn okhttp3.**
-dontwarn okio.**

Troubleshooting

SDK not initializing. Confirm configure() runs in Application.onCreate() (not an Activity), the Application class is registered in AndroidManifest.xml, and the SDK key has no whitespace. Tail adb logcat | grep LinkzlySDK.

Events not tracking. Check isTrackingEnabled(), confirm network connectivity, inspect getPendingEventCount(), and try a manual flushEvents { ... }.

Deep links not working. Verify the intent filter contains VIEW + DEFAULT + BROWSABLE, the data scheme matches, android:launchMode="singleTask" is set, and onNewIntent() is overridden. Test with adb shell am start -W -a android.intent.action.VIEW -d "yourapp://test" com.yourcompany.yourapp.

App Links not verifying. Confirm assetlinks.json is served over HTTPS at the well-known path with Content-Type: application/json and no redirects, the SHA-256 fingerprint matches your signing certificate, and re-verify with adb shell pm verify-app-links --re-verify <package>.

Install Referrer issues. The Install Referrer API requires Google Play Services and only returns referrer data for ~90 seconds post-install. It is unavailable on Huawei devices.

Gaming 401 with HMAC enabled. The signing secret in GamingOptions must match Game Settings exactly, and the device clock must be within 5 minutes of UTC.

Was this helpful?

Help us improve our documentation