Identify customers
Link anonymous purchase history to accounts in your authentication system.
Voidhash creates an anonymous distinct ID on first launch and persists it, so purchases and events
work before anyone signs in. If your app has accounts, call identify() after authentication.
Access then follows the customer across devices.
Identify after sign-in
Pass a stable identifier from your backend, plus optional profile fields:
voidhash.identify(externalUserId = user.id, email = user.email, name = user.name)Use an opaque, unique ID such as a UUID. Do not use an email address or a sequential database ID as
the primary identifier. Voidhash stores the ID exactly as you pass it. An ID Voidhash cannot store
throws a VoidhashException with the code INVALID_ARGUMENT and leaves the current identity
unchanged, whether or not the device is online: an empty or whitespace-only ID, one longer than
255 characters, one containing a NUL character, or one starting with vh:anon:, the prefix of
anonymous IDs.
When an anonymous customer signs in, Voidhash moves their purchase history and entitlements to the identified person. Events captured before the switch stay attributed to the anonymous identity.
The switch happens on the device immediately. If Voidhash cannot be reached, returns an unreadable
answer, or rejects the publishable key, identify() does not throw. It returns the person the SDK
knows about and delivers the sign-in later, including after the app restarts. Until Voidhash
confirms it, a customer who bought while anonymous keeps the access cached for the anonymous
identity, marked stale. Use identifyWithStatus() to learn whether Voidhash has the sign-in yet:
when (voidhash.identifyWithStatus(user.id).status) {
WriteStatus.CONFIRMED -> Unit // Voidhash has linked the identities
WriteStatus.DEFERRED -> Unit // applied on the device, delivered once Voidhash is reachable
}identify() throws only when Voidhash refuses the sign-in itself: a 400 for an invalid request
or a 404 for an unknown target. The SDK then restores the previous identity. See
Errors.
When the anonymous identity already belongs to another account, Voidhash answers 409 and cannot
merge it. identify() does not throw in that case. The device switches to the customer you
identified, without the anonymous identity's history and purchases, which stay with the other
account, and the SDK reports a PERSON_WRITE_REJECTED diagnostic. A sign-in delivered later
behaves the same way.
Follow authentication state
Wait until your authentication system has finished loading. Then call identify(user.id) for a
signed-in user, or signOut() once sign-out is confirmed. A loading session is not a signed-out
session; resetting during startup discards the persisted identity before authentication restores it.
Identity changes are ordered by the SDK, including calls during initialization. A sign-in that is
waiting on the network never delays initialize() or signOut(). Repeating identify() for the
same ID without profile changes leaves the session and cached state intact. Switching directly
between accounts does not merge them. Events and queued transactions retain the identity that
captured them, and purchases retain the identity that started them.
If you already know the customer when you configure the SDK, you can pass their ID as the
distinctId option instead of waiting to call identify(). When the device used an anonymous
identity before, initialize() links it to that ID, so earlier purchases carry over. A
distinctId that identify() would refuse, such as a blank one, is ignored.
No declarative identity prop, additional provider, or app-side identity queue is needed.
Sign out
Sign out of Voidhash after your own sign-out completes:
voidhash.signOut()This records a sign-out event when automatic lifecycle events are enabled, then starts a fresh
anonymous identity and session. Shared caches and pending deliveries survive. reset() remains
available with the same behavior.
Set customer attributes
Write profile traits for the current person:
voidhash.setPersonAttributes(
mapOf(
"email" to "ada@example.com",
"name" to "Ada Lovelace",
"plan_source" to "referral",
)
)Attribute values can be strings, numbers, booleans, or null. A map or a list throws a
VoidhashException with the code INVALID_ARGUMENT before anything is sent, whether or not the
device is online. Non-finite numbers are sent as null. An empty map sends nothing. email and
name map to built-in person fields. Other keys become custom traits.
Like identify(), attribute writes never fail because Voidhash cannot be reached. They are queued
and delivered later. Attributes set right after a sign-in that is still queued wait for it, so they
always land on the identified person.
Read the distinct ID
Read the distinct ID of the current identity, anonymous or identified:
val distinctId = voidhash.getDistinctId()This is useful in support logs, and when you correlate a client session with your backend.
Anonymous or identified?
Which setup fits depends on how your app handles accounts.
- No accounts. Keep the automatically generated anonymous identity.
- Optional accounts. Let purchases work anonymously, then identify after sign-in.
- Account required. Identify immediately after restoring your app session.