VoidhashDocs
DocumentationAPI reference
Dashboard
Get started
Introduction
Quickstart
Setup
Setup overview
Create a project
Products and perks
Paywall locations
CLI
Paywall components
Components
Develop a component
Editor panels
Component definition
Publishing
Installing and capabilities
SDKs
Swift
Kotlin
React Native
Web
Store setup
Connect the App Store
Connect Google Play
Connect Stripe
API libraries
Node.js
PHP
Rust
Go
Resources
MCP server
Meta ads
Migrate in observer mode
Delete a person's data
Web SDK
Web quickstart
Identify users
Track events
Page views and attribution
Stripe revenue
Web-to-app handoff
First-party proxy
Feature flags
Troubleshooting

Web SDK

Configure @voidhash/web and look up its options, methods, React bindings, and events.

The web SDK, @voidhash/web, brings Voidhash analytics and attribution to websites and web apps: page views, product events, sessions, the campaign or ad that brought each visitor, and the person behind them. Purchases on the web go through Stripe, and Voidhash records them from Stripe itself; the SDK supplies the visitor's id so each purchase lands on the right person and campaign. A visitor who signs in on your website and in your iOS, Android, or React Native app is one person in Voidhash.

Use this page to create the client, set its options, and find the method, hook, or event you need. Each feature has its own guide, linked from the section that introduces it. To get started, follow the quickstart.

Compatibility

RequirementVersion
BrowsersCurrent versions of Chrome, Edge, Firefox, and Safari. The package targets ES2022.
effectPeer ^4.0.3. Install 4.0.3 or a later 4.x release.
reactOptional peer, only for @voidhash/web/react. React 18 or later.
FrameworksAny. Next.js, React Router, TanStack Router, Vue, Svelte, and plain HTML pages all work.
RenderingServer rendering is safe: creating the client and rendering the provider never touch the DOM.
ModulesES modules for browsers and bundlers. The CommonJS build needs Node 20.19 or 22.12 or later, because effect is an ES module, and its two entry points do not share classes, so instanceof does not hold across them.

The SDK runs in the browser. Call initialize() only in the browser; on a server it rejects with an error that says it needs a browser. To send events from your own backend, use the Event Capture API or the Node.js library.

Data collection

Account for the data the SDK sends to Voidhash in your privacy policy and cookie banner.

DataWhen the SDK collects itUsed for
Anonymous IDAlways. The SDK creates one per browser and stores it in localStorage.Identity, analytics
User IDWhen you call identify() with your own user ID.Identity, analytics
Email address and nameOnly when you pass them to identify() or set them as person attributes.Identity
Pages and product eventsWhile analytics consent is granted, the default. The page's origin, path, and title, the referrer's origin and path, and the events you track.Analytics
Campaign and ad clicksWhile analytics consent is granted. utm_* parameters, ad IDs, and Meta's _fbc and _fbp cookies.Attribution
Device detailsWith every request: the browser user agent, language, and platform. With events, while consent is granted: time zone and screen and viewport size.Analytics
Coarse locationWhile analytics consent is granted. Voidhash derives a city-level location from the request's address.Analytics

The SDK never records query strings other than the campaign parameters, never sets cookies, and does not share data with other companies. It reads Meta's cookies only when your site already sets them. It stores its identifiers, queue, and settings in localStorage, and a campaign seen before consent in sessionStorage; many consent regimes treat that storage like cookies, so ask for consent before analytics where your visitors require it. See Analytics consent.

Create the client

Create one client per page and keep it for the lifetime of the page.

src/lib/voidhash.ts
import { createVoidhashClient } from "@voidhash/web";

export const voidhash = createVoidhashClient({
  publishableKey: "vh_pk_…",
});

The publishable key is safe to ship in a web page. Never put a vh_sk_… secret key in browser code.

initialize() loads the SDK's saved state, records the first page view, and starts sending events in the background. It resolves without waiting for the network, so a slow or unreachable Voidhash never delays your page. Calls made while it runs wait for it, so an event tracked during page load is kept; calls made before you call initialize() reject with VoidhashNotInitializedError. In React, the provider initializes the client for you.

await voidhash.initialize();

Create one client per project on a page. Two clients for the same project each record page views, so every page would be counted twice.

Client options

OptionDefaultDescription
publishableKeynoneRequired. Your project's vh_pk_… key.
baseUrlhttps://api.voidhash.comAPI origin. Point it at a first-party proxy or a self-hosted deployment.
analyticsConsent"granted"Consent to start with: "granted", "denied", or "pending". A choice stored by setAnalyticsConsent() wins.
distinctIdStored or new anonymous IDSeeds the identity. Usually omit it and call identify().
onDiagnosticnoneCalled for every SDK diagnostic. See Troubleshooting.
analyticsSee belowPage view and event delivery options.
featureFlagsSee belowFeature flag caching options.
observerModefalseHas no effect in the browser.

analytics takes these fields.

FieldDefaultDescription
capturePageViewstrueRecords $pageview on load and after every client-side navigation. Attribution is recorded either way.
enabledtruefalse turns analytics off, as if consent were denied, without storing a choice.
baseUrlClient's baseUrlOrigin for analytics only, when it differs from the API origin.
flushIntervalMs5000How often queued events are sent.
maxBatchSize20Events per request. Reaching it sends the batch at once.
maxBatchBytes262144Upper bound of one request body, in bytes.
maxQueueSize1000Events kept while offline. The oldest are dropped first.

featureFlags takes these fields.

FieldDefaultDescription
ttlMs300000Age after which cached flags are refreshed in the background.
persisttrueKeeps flags in localStorage so the next page load starts with them.
prefetchOnInitfalseEvaluates every flag during initialize(), in the background.
refreshOnOnlinetrueRefreshes flags when the browser reconnects, at most once a minute.
refreshOnVisibilitytrueRefreshes flags when the tab becomes visible, at most once a minute.

createVoidhashClient throws a VoidhashConfigurationError for a missing publishableKey, a secret vh_sk_… key, a URL that is not an absolute http:// or https:// URL, a consent value other than the three above, or a size or interval that is not a positive integer. Blank strings count as not set.

Client methods

Methods that return a promise wait for a running initialize(). They reject with VoidhashNotInitializedError when it was never called and with VoidhashDestroyedError after destroy(). Network failures reject only setPersonAttributesSync(), which writes to Voidhash directly. Everything else waits for the network on its own: events and identity links stay in the queue until Voidhash accepts them, and reads answer from the cache.

MethodReturnsDescription
initialize()Promise<void>Loads saved state and starts the SDK. Safe to call more than once.
destroy()Promise<void>Sends what it can, removes listeners, and stops the client for good.
track(name, properties?, options?)Promise<void>Queues a product event; never waits for the network. See Track events.
page(name?, properties?, options?)Promise<void>Records a $pageview yourself. See Page views.
identify(userId, traits?)Promise<void>Switches to your user ID and links the visitor in the background. See Identify users.
signOut()Promise<void>Records $sign_out, then starts a new anonymous identity and session.
reset()Promise<void>Starts a new anonymous identity and session without recording a sign-out.
setPersonAttributes(attributes)Promise<void>Queues person attributes, delivered with the events.
setPersonAttributesSync(attributes)Promise<VoidhashPerson>Writes person attributes now and returns the person, { personId, distinctId, email, name }. Rejects when Voidhash cannot be reached.
getDistinctId()string | undefinedThe current anonymous or identified ID. Pass it to Stripe; see Stripe revenue.
getStripeClientReferenceId()string | undefinedThe distinct ID in the form Stripe Payment Links and the pricing table accept; undefined for a user ID they would drop. See Stripe revenue.
getSessionId()string | undefinedThe session the next event joins. undefined without consent or after 30 idle minutes.
setAnalyticsConsent(consent)Promise<void>Stores and applies the visitor's choice. Works before initialize().
getAnalyticsConsent()AnalyticsConsentThe consent in effect.
flushAnalytics()Promise<AnalyticsFlushStatus>Sends queued identity links and events now. Resolves with { flushed, pending, lastError? }.
getFeatureFlags(keys?)Promise<FeatureFlagsResult>Evaluates flags, cache first. See Feature flags.
refreshFeatureFlags(keys?)Promise<FeatureFlagsResult>Evaluates flags from the network.
isFeatureEnabled(key)booleanThe last evaluated value, synchronously. Throws before initialize().
getFeatureVariant(key)FeatureFlagEntry | undefinedThe last evaluated flag, synchronously. Throws before initialize().
on(event, handler), off(event, handler)() => void from onSubscribes to client events. on returns the unsubscribe function.
onDiagnostic(handler)() => voidSame as on("diagnostic", handler).

The client takes and returns plain values; you need no effect code to use it.

Client events

Subscribe with voidhash.on(name, handler).

EventPayloadWhen
initialized{ distinctId }initialize() has finished.
identity-changed{ distinctId, previousDistinctId }After identify(), signOut(), or reset().
analytics-consent-changed{ consent }Consent changed, in this tab or another one.
analytics-flushed{ accepted, rejected, requestId? }A batch was delivered.
analytics-partial-rejection{ accepted, rejected, requestId? }Voidhash refused some events of a batch.
feature-flags-updated{ distinctId, keys?, result }New flag values arrived for the person distinctId.
diagnosticVoidhashDiagnosticSee Diagnostics.
error{ source, message, error? }A background task failed, such as a scheduled flush.

React

The @voidhash/web/react entry point has a provider and three hooks. It is a separate entry point, so pages without React never load it.

import { VoidhashProvider } from "@voidhash/web/react";

<VoidhashProvider config={{ publishableKey: "vh_pk_…" }}>
  <App />
</VoidhashProvider>;

VoidhashProvider takes either config, the client options, or client, a client you created.

  • With config, the provider creates the client, initializes it when it mounts, and destroys it when it unmounts. React Strict Mode's immediate remount keeps the same client, and a provider shown again after a Suspense boundary or <Activity> hid it starts a new one.
  • With client, the provider initializes it but never destroys it, so code outside React can keep using the same client.

The provider reads config and client once, when it mounts; to change them, mount a new provider, for example with a key.

HookReturns
useVoidhash(){ client, status, isInitialized, initError, retryInit, distinctId }. distinctId follows identify() and signOut().
useAnalytics(){ track, page, flushAnalytics, consent, enabled, setAnalyticsConsent }. Follows consent changes in other tabs.
useFeatureFlags(keys?){ data, isEnabled, getVariant, isLoading, isStale, isExpired, error, refetch }.

Hooks throw when they are used outside a VoidhashProvider. The React entry point is marked "use client", so the Next.js App Router renders it on the client; the quickstart shows the setup.

status is initializing until the client is ready, then ready. If initialize() fails, for example because the page's storage is blocked in a way the SDK cannot work around, it is failed, initError says why, and retryInit() tries again:

const { status, initError, retryInit } = useVoidhash();

if (status === "failed") {
  reportError(initError);
}

Analytics never block rendering, so most apps render as usual in every status.

Errors

Rejections are VoidhashError instances, exported from @voidhash/web:

ClassMeaning
VoidhashNotInitializedErrorThe method needs initialize() to resolve first.
VoidhashDestroyedErrorThe client was destroyed. Create a new one.
VoidhashConfigurationErrorAn option is invalid, such as a secret key or a relative baseUrl.
VoidhashInvalidArgumentErrorAn argument is invalid, such as a blank user ID. Nothing was changed.
VoidhashErrorAn operation failed. Its message starts with a code such as FAILED_TO_RESET.

Background work reports through diagnostics instead of rejecting.

Bundle size

The SDK's own code is about 19 KB gzipped. With the parts of effect it uses, a production build of a page that does not use effect otherwise grows by about 65 KB gzipped; an app that already uses effect shares it. Nothing is loaded from a CDN at runtime. To keep a landing page's first paint fast, initialize the client after your critical content, for example in an effect or with a dynamic import().

Not available on the web

The web SDK covers analytics, attribution, identity, and feature flags. These are not part of it:

  • Purchases and restore. Web purchases go through your own Stripe integration and reach Voidhash through Stripe. See Stripe revenue.
  • Paywalls. Hosted paywalls are presented by the mobile SDKs.
  • Push notifications and deep links.
  • Access checks in the browser. Check access on your server with the Node.js library.
PreviousErrorsNextWeb quickstart

On this page

CompatibilityData collectionCreate the clientClient optionsClient methodsClient eventsReactErrorsBundle sizeNot available on the web