Identify users
Link anonymous visitors to your users, and keep web and app users the same person.
Every visitor starts with an anonymous ID that the SDK creates and stores in localStorage, so page
views, campaigns, and events are recorded before anyone signs in. When a visitor signs up or signs
in, call identify() with your own user ID. Voidhash then joins the anonymous visitor to that user:
their earlier page views, the campaign that first brought them, and anything they do from now on
belong to one person.
Identify after sign-in
Pass a stable identifier from your backend, plus optional attributes:
await voidhash.identify(user.id, {
email: user.email,
name: user.name,
});Use an opaque, unique ID such as a UUID, the same one your server and your mobile app use. Do not
use an email address or a sequential database ID. IDs that start with vh:anon: are reserved for
anonymous visitors.
email and name fill the person's built-in email and name. Other keys become custom attributes.
Values can be strings, numbers, or booleans; null clears an attribute.
identify() switches the browser to the user at once and never waits for the network. Events
already recorded keep the anonymous ID they were recorded with, events from now on carry the user
ID, feature flags are evaluated again for the user, and the React hooks re-render with the new
distinctId. It rejects only for an invalid ID: a blank one, or one that starts with vh:anon:.
Identify while offline
The link that joins the anonymous visitor to the user is stored in the browser and delivered in the
background. When Voidhash cannot be reached, the browser keeps the new identity and retries the link
with growing delays, as soon as the connection comes back or the tab becomes visible again, and on
the next page load. It survives reloads and closed tabs. flushAnalytics() delivers it at once
when it can.
Identify on page load
For a signed-in user, identify on every page load, skipping the call when the SDK already uses their ID:
if (voidhash.getDistinctId() !== user.id) {
await voidhash.identify(user.id, { email: user.email });
}That covers a user who signs in on a page where the SDK is not loaded, such as a hosted sign-in page.
In React, follow your authentication state with useVoidhash().distinctId, including sign-out:
const { client, distinctId, isInitialized } = useVoidhash();
const { user, isLoading } = useAuth(); // your authentication state
useEffect(() => {
if (!isInitialized || isLoading) return;
if (user && distinctId !== user.id) {
void client.identify(user.id, { email: user.email });
} else if (!user && distinctId && !distinctId.startsWith("vh:anon:")) {
void client.signOut();
}
}, [client, distinctId, isInitialized, isLoading, user]);Wait until your authentication state has loaded, as isLoading does above. A session that is still
loading is not a signed-out session.
What identify merges
- A new user. The anonymous visitor becomes the user. Nothing else changes.
- An existing user, for example one who signed up in your app first. The anonymous visitor is merged into that user, and the older of the two people is kept. Their page views, campaign traits, purchases, and subscriptions end up on one person.
- Switching accounts. Calling
identify()with another user ID while a user is signed in does not merge the two users, the same as in the mobile SDKs. Events from then on carry the new user, who starts from their own history and gets the email, name, and attributes you pass. The switch records no sign-out, so callsignOut()when a user signs out, for example on a shared computer. - The same user again. Nothing is merged. New email, name, or attributes are written to the user; a call without them sends nothing.
A retried or repeated link changes nothing the second time.
Keep web and app users the same person
Call identify() with the same user ID on the web and in your iOS, Android, or React Native app.
The order does not matter: whichever side identifies first creates the user, and the other side's
anonymous visitor is merged into it. Revenue from Stripe, the App Store, and Google Play then lands
on one person, credited to the campaign that first brought them.
See Web-to-app handoff for funnels where the visitor pays on the web and continues in the app.
Sign out
Sign out of Voidhash after your own sign-out completes:
await auth.signOut();
await voidhash.signOut();signOut() records a $sign_out event for the user, then starts a new anonymous visitor and a new
session. Events recorded before keep the user's ID. Use reset() to do the same without recording
the event, for example when you clear a guest session.
Other open tabs of your site follow an identify, sign-out, or reset at once, so a tab left open never keeps recording under a user who signed out in another one.
Analytics consent is kept across sign-out and reset; it belongs to the browser, not the user.
Set person attributes
Set attributes on the current person without waiting for the server:
await voidhash.setPersonAttributes({
name: "Ada Lovelace",
plan_source: "referral",
});The update travels with the events, so it is queued, retried, and kept across page loads like them. It needs analytics consent; without it, nothing is stored or sent.
To write the attributes now and read the updated person back, use setPersonAttributesSync(),
which needs the network:
const person = await voidhash.setPersonAttributesSync({ plan_source: "referral" });Without analytics consent, it writes nothing and returns the unchanged person.
Read the distinct ID
getDistinctId() returns the current ID, anonymous or identified:
const distinctId = voidhash.getDistinctId();Send it to your server when it creates a Stripe Checkout Session or customer, so the purchase is attributed to this visitor. See Stripe revenue. It is also useful in support logs.
Seed the identity
The distinctId client option starts the client with an ID you already know:
const voidhash = createVoidhashClient({
distinctId: user.id,
publishableKey: "vh_pk_…",
});On a browser without a stored identity, the client starts as that ID. When the browser already has
another identity, initialize() identifies the user, as identify() would, without waiting for the
network. An anonymous ID seed is used only on a browser without a stored identity; two anonymous
visitors cannot be merged.