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, Dates, sent as ISO-8601 strings, and
arrays and plain objects of those, nested up to 10 levels deep. A value with a toJSON() method,
such as a URL or a date-library object, is sent as JSON.stringify would write it. Anything else
is left out: other class instances, DOM elements and events, functions, undefined, invalid dates,
values nested deeper, and references back to an enclosing object. Pass the values you need, such as a button's id,
rather than a whole element or event.
Use clear, stable event names. Names that begin with $ are reserved for Voidhash's own events, and
Voidhash discards a custom event that uses one.
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:
| Option | Default | Use it to |
|---|---|---|
eventId | A new random ID | Give an event your own ID, so the same event sent twice is stored once. |
timestamp | Now | Record when the event happened, as an ISO-8601 string. |
sessionId | The current session | Attribute the event to another session. |
Automatic events
The SDK records these events for you:
| Event | When |
|---|---|
$pageview | On load and after every client-side navigation that changes the path. See Page views. |
$set | When a visit carries campaign parameters, and when you set person attributes. |
$sign_out | When 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 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, except those sent while leaving a page (see below):
- 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 batch that fails, or that Voidhash answers with 408, 429, 500, 502, 503, or 504,
is retried with growing, randomized delays of at most 37.5 seconds, for as long as it takes.
A
Retry-Afterfrom Voidhash is honored, up to 30 seconds. - 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. Events are recorded only with analytics consent, so the queue is never stored without it. - 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. The browser sends that request even though the page never sees the answer, so the next page does not send those events again; if the request fails after the page is gone, they are lost. They are kept for the next page instead when the browser is offline, cannot keep a request going after the page, or Voidhash was failing or asked the SDK to wait. 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 with its next batch, every 5 seconds by default.
- 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 five failures in a row the SDK pauses requests for 30 seconds, doubling up to 5 minutes while they keep failing, so an outage costs your page no extra work.
Only an event Voidhash refuses for good, such as a malformed or oversized one, is dropped, besides the oldest events of a full queue and the events discarded when consent is withdrawn. 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:
- Page views, events, sessions, and person attributes set with
setPersonAttributes()are not recorded, stored, or sent.setPersonAttributesSync()writes nothing and returns the unchanged person. The first call the SDK skips is reported once as theANALYTICS_CONSENT_DENIEDdiagnostic. - Feature flags and
identify()keep working. Their requests tell Voidhash that consent is missing, so it records no analytics for them.identify()still sends the email, name, and attributes you pass to it, because purchases are attributed to the person; leave it out until consent is granted if your consent regime treats linking a visitor to your user as analytics. - A campaign the visitor arrived from while consent is
pendingis held for the browser session and sent if consent is granted in that session, unlessstoreBeforeConsentisfalse.denieddiscards it.
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.
By default, the SDK keeps its anonymous ID, cached flags, and pending identify() links in the
browser even before consent. To keep everything but the consent choice off the device until the
visitor agrees, and to connect a consent tool, see
Privacy and consent.
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.