Kotlin SDK
Client configuration and API reference for the Voidhash Android SDK.
The Voidhash Android SDK adds entitlements, feature flags, analytics, and observer-mode transaction reporting to native Android apps.
Initial release is observer-only
SDK-started purchases and hosted paywalls are temporarily unavailable. Google Play transactions are still observed and submitted to Voidhash for revenue analytics, but the SDK never acknowledges or consumes them.
Compatibility
| Requirement | Version |
|---|---|
minSdk | 23 |
compileSdk | 34 |
| Build | AGP 8.9.0, Kotlin 2.0.21, Java 8 bytecode |
| Billing | Play Billing 8.0.0 |
Two Gradle modules ship in @voidhash/android: :sdk (com.voidhash.sdk), the public SDK you
integrate against, and :core (com.voidhash.core), the shared engine it depends on.
Create the client
Create one client for the lifetime of the app:
import com.voidhash.sdk.Voidhash
import com.voidhash.sdk.VoidhashOptions
val voidhash = Voidhash.configure(
context = applicationContext,
publishableKey = "vh_pk_...",
)The publishable key is safe to include in the app. Never ship vh_sk_... secret keys.
There is no schema argument: the schema lives on the server and is fetched by initialize(), which
connects to Google Play, resolves the schema, reconciles unfinished store transactions, and starts
the analytics queue. It is safe to call repeatedly — only the first successful call does work,
concurrent callers wait for it, and a failed call leaves the client uninitialized so it can be
retried:
lifecycleScope.launch {
voidhash.initialize()
}Every method that needs the schema throws VoidhashException with code
CONFIGURATION_MISSING until initialization has succeeded.
Client options
val voidhash = Voidhash.configure(
context = this,
publishableKey = "vh_pk_...",
options = VoidhashOptions(debug = BuildConfig.DEBUG),
)| Option | Default | Use |
|---|---|---|
baseUrl | https://api.voidhash.com | Override the API origin for self-hosted deployments. |
ingestUrl | Same origin as baseUrl | Override only the analytics origin. |
debug | false | Enable verbose logging and mark requests as debug-build traffic. |
distinctId | Generated anonymous ID | Seed the initial customer identity. Usually omit it and call identify(). |
enabled | true | Set false to ship the SDK fully inert. |
readOnly | true | Forced on while commerce features are unavailable. |
Every method on VoidhashClient other than capture, getDistinctId, and setReadOnly is a
suspending function: the SDK never blocks the caller's thread and never posts work back to the main
thread on its own.
Disabled clients
With enabled: false, every method is inert: no requests are made, no billing connection opens,
getProducts() returns an empty list, and getCurrentPerson() returns null.
Observer mode
The initial release always uses observer mode. Passing readOnly = false or calling
setReadOnly(false) cannot transfer Google Play ownership to Voidhash yet.
Products and transaction reporting
val products = voidhash.getProducts()
voidhash.restorePurchases()Initialization and restore submit observed Google Play transactions to Voidhash without
acknowledging or consuming them. purchase(...) is retained for a later commerce launch but
currently raises READ_ONLY_PURCHASE_NOT_ALLOWED before Play Billing is touched.
People and entitlements
voidhash.getDistinctId()
voidhash.identify(externalUserId = "user-123", email = "a@b.co", name = "Ada")
voidhash.setPersonAttributes(mapOf("plan" to "pro"))
val person = voidhash.getCurrentPerson(forceFetch = true)
person?.activePerkIds
voidhash.reset() // Sign out: clears the local identity and cacheThe person snapshot is cached for two days and served stale after five minutes; pass
getCurrentPerson(forceFetch = true) to bypass the cache. The SDK refreshes it after purchases,
restores, and identity changes.
See Check access and Identify customers for the underlying concepts.
Feature flags
val flags = voidhash.getFeatureFlags(listOf("new_onboarding"))
val enabled = flags.firstOrNull { it.key == "new_onboarding" }?.enabled == truePass an empty list to evaluate every flag. See Evaluate feature flags for variants and identity behavior.
Analytics
voidhash.capture("checkout_started", mapOf("source" to "paywall"))
voidhash.flush()Events are batched (20 events or every 5 seconds) and retried with exponential backoff, honoring
Retry-After. Names beginning with $ are reserved for Voidhash events. See
Capture analytics.
Paywalls
Hosted paywalls are temporarily unavailable. presentPaywall(...) returns false, paywall
resolution returns null, and no network or presentation work is performed.
Shutdown
voidhash.shutdown()Flushes analytics and ends the Play Billing connection. Call it when the process is going away for good, not on every background event.