VoidhashDocs
DevelopersHelp centerGuidesAPI reference
voidhash.comOpen Studio
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
Theme

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 tell the SDK what you found, on every launch and whenever the session changes: identify for a signed-in user, signOut when there is none. Don't sign out while your session is still loading: that discards the stored identity before your authentication restores it.

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

You don't need to remember what you called before. Repeating identify with the same ID, email and name sends nothing; a changed email or name is sent. signOut does nothing while nobody is signed in, so an anonymous person keeps the identity that holds their purchases. If your users belong to companies or workspaces, pass the full set to setGroups in the same place.

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. While nobody is signed in, signOut does nothing: no event, no new anonymous ID, and the groups stay.

reset() does exactly the same for a signed-in user. The two differ only when nobody is signed in: signOut() does nothing, and reset() starts a fresh anonymous identity, with a new ID, no groups and a new session, without recording $sign_out. Use reset() on a shared device, for a guest's "start over", and in tests.

A consent that identify granted with grantConsentOnIdentify lasts as long as the sign-in: both calls return it to PENDING.

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, as strings, set the person's own email and name, as identify does; 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. Attributes are sent whatever the analytics consent, as identify is. The one exception is agreeing to ad data sharing, which needs consent: a write holding nothing else returns SKIPPED.

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
© 2026 Voidhash s.r.o.
voidhash.comPricingTermsPrivacyDiscord

On this page

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