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

RequirementVersion
minSdk23
compileSdk34
BuildAGP 8.9.0, Kotlin 2.0.21, Java 8 bytecode
BillingPlay 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),
)
OptionDefaultUse
baseUrlhttps://api.voidhash.comOverride the API origin for self-hosted deployments.
ingestUrlSame origin as baseUrlOverride only the analytics origin.
debugfalseEnable verbose logging and mark requests as debug-build traffic.
distinctIdGenerated anonymous IDSeed the initial customer identity. Usually omit it and call identify().
enabledtrueSet false to ship the SDK fully inert.
readOnlytrueForced 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 cache

The 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 == true

Pass 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.