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