VoidhashDocs
DevelopersHelp centerGuidesAPI reference
Dashboard
Get started
Introduction
Quickstart
How Voidhash works
Glossary
Versions and releases
Concepts
Teams, projects and environments
API keys
Products and perks
Offers
Subscription lifecycle
People and identity
Groups
Events
Features
Paywalls in your app
Feature flags and A/B tests
Paywall components
Push notifications
Ad attribution in your app
Client SDKs
React Native
Swift
Kotlin
Web
Your backend
Node.js
PHP
Go
Rust
Other languages
Payment sources
Forward store notifications
Developer tools
CLI
MCP server
Query analytics with VoidQL
Kotlin SDK
Kotlin quickstart
Display a paywall
Make and restore purchases
Check access
Identify users
Evaluate feature flags
Capture analytics
Groups
Track screens
Development mode
Kotlin SDK reference
Errors
Diagnostic codes
Troubleshooting

Identify users

Link a person's purchases and access to the account they sign in with.

Call identify when someone signs in, so their purchases and access follow them to every device. Until then the SDK uses an anonymous distinct ID, so purchases and events work before sign-in. See People and identity for how anonymous and identified people merge.

Identify after sign-in

Pass your stable user ID, and optionally an email and name:

voidhash.identify(externalUserId = "user_123", email = "ada@example.com", name = "Ada Lovelace")

Use an opaque ID such as a UUID, not an email address. identify throws INVALID_ARGUMENT and keeps the current identity when the ID is empty or whitespace, longer than 255 characters, contains a NUL character, or starts with vh_anon_ or vh:anon:, the prefixes of anonymous IDs.

The switch happens on the device at once. If Voidhash can't be reached, identify doesn't throw: it returns the person the SDK knows and delivers the sign-in later, across restarts. Until then, access bought while anonymous stays cached, marked stale. identifyWithStatus tells you whether Voidhash has the sign-in:

import com.voidhash.sdk.WriteStatus

when (voidhash.identifyWithStatus("user_123").status) {
    WriteStatus.CONFIRMED -> Unit // Voidhash linked the identities
    WriteStatus.DEFERRED -> Unit  // applied on the device, delivered when Voidhash is reachable
    WriteStatus.SKIPPED -> Unit   // not used by identify
}

identify throws a VoidhashApiException only when Voidhash refuses the sign-in: 400 for an invalid request, 404 for an unknown target. The SDK then restores the previous identity.

An anonymous ID can belong to only one account

If this device's anonymous ID already belongs to another identified person, identify doesn't throw. The device switches to the new user without the anonymous history and purchases, which stay with the other account, and the SDK reports the PERSON_WRITE_REJECTED diagnostic.

Follow your sign-in state

Wait until your authentication has loaded. Then call identify for a signed-in user, or signOut once sign-out is confirmed. Don't sign out while your session is still loading: that discards the stored identity before your authentication restores it.

Sign out only an identified person. signOut starts a new anonymous identity, so calling it for an anonymous person loses the identity that holds their purchases. Use isAnonymousDistinctId to tell:

import com.voidhash.sdk.isAnonymousDistinctId

if (user != null) {
    voidhash.identify(user.id)
} else if (!isAnonymousDistinctId(voidhash.getDistinctId())) {
    voidhash.signOut()
}

Repeating identify with the same ID and profile changes nothing. Switching from one account to another doesn't merge them; events and purchases keep the identity they were made under. The SDK orders identity calls, and a sign-in waiting on the network never delays initialize or signOut.

If you know the user when the app starts, pass their ID as the distinctId option instead. initialize() then links the device's earlier anonymous ID to it. An ID identify would refuse is ignored.

Sign out

Call signOut after your own sign-out completes:

voidhash.signOut()

It records $sign_out, then starts a new anonymous identity and session, and clears that person's cached access, flags, saved flow progress and groups. Queued deliveries are kept. reset() does the same.

Set person attributes

Write attributes on the current person:

voidhash.setPersonAttributes(
    mapOf(
        "email" to "ada@example.com",
        "plan_source" to "referral",
    ),
)

Values can be strings, numbers, booleans or null; a map or list throws INVALID_ARGUMENT before anything is sent. email and name set the built-in fields; other keys become custom attributes. An empty map sends nothing.

Attribute writes never fail because Voidhash is unreachable: they're queued, and ones set right after a queued sign-in wait for it. setPersonAttributesWithStatus returns the WriteStatus; SKIPPED means analytics consent isn't granted, so attributes aren't collected.

Read the distinct ID

getDistinctId() returns the current anonymous or identified ID, without suspending. Use it in support logs or to match a session with your backend.

val distinctId = voidhash.getDistinctId()

The SDK stores it in your app's private shared preferences. See Data collection for how backups affect it.

Next steps

  • Check access for the identified person.
  • Capture analytics.
  • People and identity.

Something wrong or missing on this page? Tell us by email or on Discord.

PreviousCheck accessNextEvaluate feature flags

On this page

Identify after sign-inFollow your sign-in stateSign outSet person attributesRead the distinct IDNext steps