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
Swift SDK
Swift quickstart
Display a paywall
Make and restore purchases
Check access
Identify users
Evaluate feature flags
Capture analytics
Groups
Track screens
Development mode
Swift SDK reference
Errors
Diagnostic codes
Troubleshooting

Identify users

Link a device's anonymous history to your user IDs, set attributes, and sign out.

The SDK starts every install with an anonymous distinct ID, so purchases and events work before anyone signs in. Call identify after sign-in to link the device to your user ID; purchases and access then follow the person across devices and platforms. Distinct IDs, merging and attributes are explained in People and identity.

The anonymous ID has the form vh_anon_<uuid> and is stored in UserDefaults, so it doesn't survive reinstalling the app. A user who bought anonymously gets access back on a new install by restoring purchases.

Identify after sign-in

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

try await voidhash.identify(externalUserId: "user_123", email: "ada@example.com", name: "Ada")

Use an opaque, unique ID such as a UUID, not an email address or a sequential database ID. The ID must contain a non-whitespace character, be at most 255 characters long, contain no NUL character, and not start with vh_anon_ or vh:anon:. Otherwise identify throws INVALID_ARGUMENT and keeps the current identity.

When an anonymous person signs in, Voidhash moves their purchases and entitlements to the identified person. Events captured before the switch stay on the anonymous distinct ID. Calling identify again with the same ID and no profile changes does nothing.

Offline and failures

The identity switches on the device at once, whether or not Voidhash is reachable. The SDK saves the link before sending it and keeps sending it until Voidhash applies it, across outages and launches. Queued links go before queued purchases, so a purchase made anonymously is recorded after the merge.

identify waits up to 2 seconds for Voidhash. Until Voidhash confirms the link, the person and feature flags keep what the anonymous person had, marked stale, so a user who bought before signing in keeps access. identifyState returns whether the link is confirmed or still deferred:

let result = try await voidhash.identifyState(externalUserId: "user_123")
if result.status == .deferred {
    // Signed in on this device; Voidhash applies the link once it's reachable.
}

identify throws only when Voidhash rejects the link for good within those 2 seconds; the SDK then restores the previous identity. It doesn't throw for network failures or a rejected publishable key. A rejection that arrives later keeps the user signed in, drops the link, and reports the PERSON_WRITE_REJECTED diagnostic.

When the anonymous ID already belongs to another identified person, for example after restoring an old device backup, identify doesn't throw either. The user is signed in as the new ID without the anonymous ID's history, which stays with the other person, and the SDK reports PERSON_WRITE_REJECTED.

Start with a known ID

If you know the user when the app starts, pass the ID as options.distinctId instead of calling identify. The SDK adopts it without a request and links an anonymous ID the device used before in the background. An invalid distinctId is ignored.

Follow your sign-in state

Wait until your authentication has finished loading, then identify a signed-in user or sign out a signed-out one. Signing out while your session is still loading discards the identity before authentication restores it. Sign out only an identified user: signOut() always starts a new anonymous identity, which would leave an anonymous buyer's purchases behind.

func sync(signedInUserId: String?) async throws {
    guard let voidhash = Voidhash.shared else { return }
    if let userId = signedInUserId {
        try await voidhash.identify(externalUserId: userId)
    } else if !Voidhash.isAnonymousDistinctId(await voidhash.getDistinctId()) {
        await voidhash.signOut()
    }
}

Switching directly from one user ID to another doesn't merge them. Events and queued purchases keep the identity they were captured under.

Sign out

Call signOut() after your own sign-out completes:

await voidhash.signOut()

It records $sign_out (when lifecycle events and analytics consent are on), then starts a new anonymous identity and analytics session, outside the user's groups. Queued deliveries, analytics consent and the install record survive. reset() does the same.

Set person attributes

try await voidhash.setPersonAttributes([
    "email": .string("ada@example.com"),
    "plan_source": .string("referral"),
    "seats": .number(3),
])

Values can be strings, numbers, booleans or nil. email and name set the built-in fields; other keys become custom attributes. An object or array value throws INVALID_ARGUMENT and nothing is saved. A number that isn't finite is written as nil.

The call waits up to 2 seconds. When Voidhash can't confirm in time, the attributes are saved on the device and sent later, and the call returns the cached person. Attributes Voidhash refuses later are dropped with the PERSON_WRITE_REJECTED diagnostic. setPersonAttributesState(_:) returns confirmed, deferred, or skipped when the user hasn't granted analytics consent: attributes need it.

Read the distinct ID

let distinctId = await voidhash.getDistinctId()

Use it in support logs, or to correlate a session with your backend. Voidhash.isAnonymousDistinctId(_:) tells you whether it's anonymous; don't check the prefix yourself.

Next steps

  • Check access
  • People and identity
  • Capture analytics

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

PreviousCheck accessNextEvaluate feature flags

On this page

Identify after sign-inOffline and failuresStart with a known IDFollow your sign-in stateSign outSet person attributesRead the distinct IDNext steps