Android (Kotlin)
Install the Android (Kotlin) SDK, register installs, track events, and read attribution. Setup takes about 5 minutes.
Requirements
- Android 7.0+ (API 24+)
- compileSdk 36+
- Java 17 (Kotlin 2.0+ for source builds)
Install
Maven Central
// app/build.gradle.ktsdependencies {implementation("sh.postback:sdk:2.0.1")}
Sync Gradle after adding the dependency. The SDK already declares INTERNET, ACCESS_NETWORK_STATE, and com.google.android.gms.permission.AD_ID, so you do not need to add them to your manifest.
Published on Maven Central. Always use the latest version.
Configure
import sh.postback.sdk.Postbackimport sh.postback.sdk.PostbackConfigclass MyApplication : Application() {override fun onCreate() {super.onCreate()Postback.shared(applicationContext).configure(PostbackConfig(apiKey = "pb_android_live_xxxxx"))}}
Non-blocking. configure() returns immediately; install registration and device-info collection run off the main thread on the SDK's executor. The lifecycle observer is registered automatically.
Track events
// Standard eventval postback = Postback.shared(applicationContext)postback.sendEvent(PostbackEventType.LOGIN)
// Revenue event. Currency must be exactly three ASCII letters.postback.sendEvent(PostbackEventType.PURCHASE,params = mapOf("revenue" to 9.99, "currency" to "USD"))
// Custom event with parameterspostback.sendEvent(PostbackEventType.CUSTOM,name = "level_complete",params = mapOf("level" to 5, "score" to 1200))
- Custom events require a trimmed, non-empty name of 1-255 UTF-16 code units with no U+0000 character. Invalid custom events are not queued.
- For standard event types, an invalid optional name is omitted and the event is still queued.
- Currency must be exactly three ASCII letters. Lowercase values are normalized to uppercase; an invalid currency field is omitted and the event is still queued.
Read attribution
Once install registration completes, the SDK caches the attribution result from the install response. Native iOS and Android expose synchronous getters; React Native and Flutter bridge calls are asynchronous.
val attr = postback.getAttribution()println(attr?.source) // "apple_ads", "tracking_link", or "organic"println(attr?.isAttributed) // Booleanprintln(attr?.campaignName) // Campaign name when availableprintln(attr?.link?.name)
Verify the connection
Send a test event to confirm end-to-end delivery. You should see it in the Postback dashboard within seconds.
val result = postback.sendTestEvent()println("${result.success} — ${result.message}")
Reference
Configuration options
| Option | Type | Default | Description |
|---|---|---|---|
| apiKey | String | required | Your Android live API key (starts with pb_android_live_). |
| apiUrl | String | https://api.postback.sh | Override for staging or self-hosted environments. |
| customerUserId | String? | null | Your internal user ID. Persists across launches and replays automatically if the first send fails. |
| autoTrackSessions | Boolean | true | Fires session_start on configure() and on ProcessLifecycleOwner.ON_START, debounced to one event per 30 minutes. |
| autoRefreshAttribution | Boolean | true | Refetches /v1/sdk/attribution on configure() and foreground transitions. |
| isDebug | Boolean | false | Forces logLevel = 0 (debug) and enables the optional debug overlay. |
| logLevel | Int | 2 (WARN) | 0 = DEBUG, 1 = INFO, 2 = WARN, 3 = ERROR. |
Supported event types
session_startloginsign_upregisterpurchasesubscribestart_trialadd_payment_infoadd_to_cartadd_to_wishlistinitiate_checkoutview_contentview_itemsearchsharetutorial_completeachieve_levellevel_startlevel_completecustomUse custom with a name parameter for any event not in this list.
Attribution fields
| Field | Description |
|---|---|
| source | "apple_ads", "tracking_link", or "organic" |
| isAttributed | false for organic installs, true otherwise |
| matchType | Backend match method: apple_ads, click_id, gaid, ttclid, fbclid, gclid, gbraid, wbraid, ip_user_agent (Android only), or organic |
| campaignName | Signal Campaign name when available |
| link | Signal link object: id, name |
| appleAds | Apple AdServices payload: campaignId, adGroupId, keywordId, countryOrRegion, conversionType |
| utmSource | UTM source from the signal link |
| utmMedium | UTM medium from the signal link |
| utmCampaign | UTM campaign value from the signal link |
API reference
configure(config)
Initializes the SDK. Returns immediately; install registration and lifecycle wiring run on the SDK executor.
postback.configure(PostbackConfig(apiKey = "…"))
sendEvent(type, name?, params?)
Enqueues an event locally and schedules a flush. Custom events require a trimmed name of 1–255 UTF-16 code units.
postback.sendEvent(PostbackEventType.PURCHASE, params = mapOf("revenue" to 9.99, "currency" to "USD"))
flush()
Drains the queue immediately.
postback.flush()
refreshAttribution()
→ AttributionResult?Fetches the latest attribution. Blocking; call from a background thread. Self-heals on 404 install_not_found.
val attr = postback.refreshAttribution()
setCustomerUserId(id)
Updates the customer user ID. Persists to disk and retries on the next configure() or foreground if the first send fails.
postback.setCustomerUserId("user-123")
getPostbackId()
→ String?Returns the install ID, or null before install registration completes.
val id = postback.getPostbackId()
getAttribution()
→ AttributionResult?Returns the cached AttributionResult.
val attr = postback.getAttribution()
getAttributionParams()
→ Map<String, String>Flat attribution/debug payload for custom integrations. For RevenueCat, set only the postbackId subscriber attribute.
val params = postback.getAttributionParams()
isInitialized()
→ BooleanTrue after configure() returns.
postback.isInitialized()
isSdkDisabled()
→ BooleanTrue if a 401 or 403 permanently disabled the SDK.
postback.isSdkDisabled()
sendTestEvent()
→ TestEventResultPosts a diagnostic event off the main thread. From the main thread, use sendTestEventAsync(callback).
val result = postback.sendTestEvent()
clearData()
Wipes local state and the event queue.
postback.clearData()
Offline behavior
- Events that fail to send are queued in native storage (up to 100 events).
- The queue persists across app restarts.
- Queued events are retried when
configure()completes, when another event is sent, when lifecycle flushes run, or when you callflush(). - If a queued event receives a 401/403, the SDK disables itself and clears the queue.
Platform notes
- configure() is thread-safe (@Synchronized) and returns immediately. Safe to call from Application.onCreate() or an activity.
- Apple Search Ads attribution is iOS-only and is not part of the standalone Android SDK API.
- GAID is read during install registration only, off the main thread. The SDK honors Limit Ad Tracking and drops the all-zero advertising ID so a bogus value never reaches the backend.
- Play Install Referrer is collected automatically. The package visibility entry for com.android.vending is in the SDK manifest, so it works on apps targeting Android 11+.
- If your app cannot collect advertising IDs (children's apps, regional policies), remove com.google.android.gms.permission.AD_ID in your host manifest with tools:node="remove".
- Events persist in SharedPreferences. They flush on foreground (ProcessLifecycleOwner.ON_START), background, the next sendEvent, or an explicit flush() call.
Next steps
Troubleshooting
| Problem | What to try |
|---|---|
| getPostbackId() returns null | configure() returns before install registration finishes. Check isInitialized() and retry briefly, or read the value inside an event handler that fires after first launch. |
| Events do not appear in dashboard | Confirm the API key starts with pb_android_live_. Call sendTestEvent() and inspect the result. Lower logLevel to 0 and check logcat under the Postback tag. |
| SDK disabled after 401/403 | A rejected key disables the SDK permanently. Call clearData(), then configure() with a valid key. |
| Events lost after app kill | Events persist to SharedPreferences and retry delivery on the next configure(). If they still do not appear, the queue may have been dropped by clearData() or hit the 100-entry cap. |
| AD_ID permission rejected by Play review | Either include advertising ID collection in your Data safety answers, or remove the permission with tools:node="remove" in your host manifest. |