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
Ad measurement
Meta ads
Migrate in observer mode
Delete a person's data
Web SDK
Web quickstart
Script tag
Identify users
Track events
Privacy and consent
Page views and attribution
Stripe revenue
Web-to-app handoff
First-party proxy
Tracking domain
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
BrowsersChrome and Edge 93 or later, Firefox 92 or later, and Safari 15.4 or later, on desktop and mobile. The package targets ES2022.
effectPeer ^4.0.3. Install 4.0.3 or a later 4.x release; an app that already uses effect 4 keeps its version.
reactOptional peer, only for @voidhash/web/react. React 18 or later.
FrameworksAny app built with a bundler: Next.js, React Router, TanStack Router, Vue, Svelte, and others. Sites without one, such as Webflow or WordPress, use the script tag.
RenderingServer rendering is safe: creating the client and rendering the provider never touch the DOM.
ModulesES modules for browsers and bundlers, and a script-tag build that includes everything it needs. There is no CommonJS build, as effect is published as ES modules only.

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

The SDK sends Voidhash the visitor's anonymous ID, your user ID once you identify them, the pages they view and the events you track, the campaign that brought them, device details, and the IP address their events come from. It never sets cookies, and it keeps its own state in localStorage and sessionStorage, which many consent regimes treat like cookies.

Privacy and consent lists exactly what the SDK collects and what it stores in the browser, for your privacy policy and cookie banner. It also shows how to ask for consent before anything is stored, and how to erase a visitor's data.

Content Security Policy

The SDK only makes fetch requests. If your site sets a Content Security Policy, allow the origin of baseUrl in connect-src, and the origin of analytics.baseUrl if you set it:

Content-Security-Policy: connect-src 'self' https://api.voidhash.com

With a first-party proxy on your own origin, 'self' covers it; with a tracking domain, allow that domain instead of api.voidhash.com. The SDK loads no scripts, styles, images, fonts, or workers and never evaluates code, so it needs nothing in other directives, and no 'unsafe-eval'.

Event batches are sent as text/plain without custom headers, so a cross-origin batch needs no CORS preflight request. Feature flag, identify(), person, and checkout requests carry the SDK's own x- headers, so the browser sends a preflight OPTIONS request before them, which Voidhash answers. Through a proxy on your own origin, no request needs a preflight.

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.
storeBeforeConsenttruefalse keeps the SDK's state in memory, not in browser storage, until analytics consent is granted. See Ask for consent first.
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.

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, at most 100; a larger value counts as 100. Reaching it sends the batch at once.
maxBatchBytes262144Upper bound of the events in one request, in bytes. An event larger than this is sent on its own.
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; with storeBeforeConsent: false, only once consent is granted.
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 and gives up after 10 seconds. 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 or does not answer within 10 seconds.
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 }A batch was delivered.
analytics-partial-rejection{ accepted, rejected }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

With the parts of effect it uses, the SDK adds about 36.4 kB gzipped to a page that does not use effect otherwise, and the React entry about 1.6 kB more. About 21 kB of that is the SDK's own code; an app that already uses effect shares the rest. The script-tag file, which includes everything, is about 36.9 kB gzipped and loads from Voidhash's CDN. The npm package loads nothing 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 collectionContent Security PolicyCreate the clientClient optionsClient methodsClient eventsReactErrorsBundle sizeNot available on the web