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
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:
- Go to Dashboard > Apps and click "Register App".
- Enter your Android Package Name and SHA-256 certificate fingerprints.
- Choose a verification method (Hosted is recommended for a quick start).
- 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.jsonin 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