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

Track events

Record product events and sessions, deliver them reliably, and respect analytics consent.

The SDK records product events and sends them to Voidhash for you. Every event is attributed to the current visitor, carries a session, and is delivered in background batches that survive network outages, page reloads, and closed tabs. It uses the same event model as the iOS, Android, and React Native SDKs, so a funnel can start on your website and finish in your app.

Track an event

Call track with an event name and the properties you want to attach:

await voidhash.track("upgrade_clicked", {
  plan: "yearly",
  source: "pricing_page",
});

track() adds the event to the queue and resolves; it never waits for the network and never rejects because Voidhash is slow or unreachable. A call made while initialize() is still running waits for it, so you can track from the first click.

Property values can be strings, numbers, booleans, null, arrays, nested objects, and Dates, sent as ISO-8601 strings. Functions and undefined values are left out. Use clear, stable event names; names that begin with $ are reserved for Voidhash's own events.

In React, useAnalytics() returns the same track bound to the provider's client:

const { track } = useAnalytics();

Track options

The third argument of track() and page() overrides what the SDK fills in:

OptionDefaultUse it to
eventIdA new random IDGive an event your own ID, so the same event sent twice is stored once.
timestampNowRecord when the event happened, as an ISO-8601 string.
sessionIdThe current sessionAttribute the event to another session.

Automatic events

The SDK records these events for you:

EventWhen
$pageviewOn load and after every client-side navigation that changes the path. See Page views.
$setWhen a visit carries campaign parameters, and when you set person attributes.
$sign_outWhen you call signOut().

Every event also carries the page's origin, path, and title, the referrer's origin and path, the screen and viewport size, and these standard properties: $platform (web), $sdk (web), $sdk_version, $locale, $timezone, and $environment. Voidhash adds the visitor's user agent and coarse location when the event arrives.

Self-hosted projects

Self-hosted installs store $pageview and $sign_out only after they are turned on in the project's event settings, under Settings → Events in Studio.

Sessions

Every event carries a session ID, so paths and funnels can tell one visit from the next. A session starts with the first event and ends after 30 minutes without any event, the same as in the mobile SDKs. All tabs of your site share the session, and it survives reloads within those 30 minutes. signOut() and reset() start a new one.

Read the current session when you want to correlate your own data with it:

const sessionId = voidhash.getSessionId();

It is undefined while analytics consent is not granted, and after 30 minutes without events until the next event starts a new session.

Delivery

Events are delivered at least once, and stored once:

  • Batching. The queue is sent every 5 seconds, or in the background as soon as it holds a full batch, 20 events by default (analytics.maxBatchSize).
  • Retries. A failed batch is retried with growing, randomized delays of up to 30 seconds, for as long as it takes. Retry-After from Voidhash is honored.
  • Offline. The queue lives in localStorage, so it survives reloads and browser restarts. It keeps up to 1,000 events; past that, the oldest are dropped first. Coming back online, or returning to the tab, sends it at once.
  • Leaving the page. On pagehide, when the visitor closes the tab or navigates away, the SDK sends up to 64 KB of the queue in a request the browser completes after the page is gone. The rest is sent on the next visit. Switching to another tab sends nothing extra; the queue keeps being sent on its schedule.
  • Several tabs. Each tab keeps its own part of the queue, so tabs never overwrite each other. A closed tab's events are sent by another open tab within about 15 seconds.
  • Duplicates. Every event has a unique ID. A batch that reaches Voidhash twice, for example because the connection dropped before the answer arrived, is stored once.
  • Outages. Every request times out after 10 seconds. After repeated failures the SDK waits before trying again, so an outage costs your page no extra work.

Only an event Voidhash refuses for good, such as a malformed one, is dropped. Diagnostics report every drop; see Troubleshooting.

To send the queue now, for example before you navigate to another site:

const { flushed, pending } = await voidhash.flushAnalytics();

You do not need to flush around identify() or signOut(): every event keeps the ID it was recorded with. flushAnalytics() also delivers identity links still waiting to be sent.

Analytics consent

When a visitor declines usage analytics, or has not decided yet, tell the SDK:

await voidhash.setAnalyticsConsent("denied"); // "granted", "denied" or "pending"
const consent = voidhash.getAnalyticsConsent();

denied and pending behave the same. While consent is either of them:

  • Not recorded, stored, or sent: page views, events, person attributes set with setPersonAttributes(), and sessions. setPersonAttributesSync() writes nothing and returns the unchanged person. The first call the SDK skips is reported once as the ANALYTICS_CONSENT_DENIED diagnostic.
  • Stored in the browser: the anonymous ID, the consent choice, the last evaluated feature flags, and identity links that are waiting to be delivered.
  • Sent to Voidhash: feature flag requests, and identify() links with the email, name, and attributes you pass to identify(), because purchases are attributed to the person. Every request says that consent is missing, so Voidhash records no analytics for it.
  • Campaigns: a campaign the visitor arrived from while consent is pending is held in sessionStorage for the browser session and sent if consent is granted in that session. denied discards it.

Leave identify() out until consent is granted if your consent regime treats linking a visitor to your user as analytics.

Withdrawing consent discards every queued event, including a batch that is being sent, in every open tab. Granting it records from that moment on; nothing skipped in the meantime is sent later.

The choice you set is stored in the browser and wins over the analyticsConsent option on later visits. Every open tab follows it, and signOut() and reset() keep it.

Connect a cookie banner

Start the client with consent pending and pass every answer from your consent tool to the SDK. Most consent tools call you back when the visitor answers and when a stored answer is loaded on a later visit:

import { createVoidhashClient } from "@voidhash/web";

const voidhash = createVoidhashClient({
  analyticsConsent: "pending",
  publishableKey: "vh_pk_…",
});
void voidhash.initialize();

// Call this from your consent tool's callback.
export function onConsentChange(analyticsAllowed: boolean) {
  void voidhash.setAnalyticsConsent(analyticsAllowed ? "granted" : "denied");
}

In React, useAnalytics() exposes consent and setAnalyticsConsent, and re-renders when consent changes in any tab:

const { consent, setAnalyticsConsent } = useAnalytics();

if (consent !== "pending") return null;
return (
  <ConsentBanner
    onAccept={() => void setAnalyticsConsent("granted")}
    onDecline={() => void setAnalyticsConsent("denied")}
  />
);

Revenue from Stripe does not depend on consent: Voidhash records purchases from Stripe either way. Metrics built from analytics events, such as page views and campaign conversion rates, count only visitors who granted consent.

Turn analytics off

analytics: { enabled: false } turns analytics off for a client, as if consent were denied, without storing a choice. Use it for internal or staging builds.

Event Capture API

Your backend can send events of its own, such as a server-side signup, through the Event Capture API, authenticated with the publishable key. Send them with the visitor's distinct ID so they join the same person.

Next steps

  • Record page views and campaign attribution
  • Identify users
  • Send analytics through your own domain
PreviousIdentify usersNextPage views and attribution

On this page

Track an eventTrack optionsAutomatic eventsSessionsDeliveryAnalytics consentConnect a cookie bannerTurn analytics offEvent Capture APINext steps