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 quickstart

Add Voidhash analytics and attribution to a website, a React app, or a Next.js app.

This quickstart starts from an existing website and ends with page views, a product event, campaign attribution, and signed-in visitors linked to your users. It takes about ten minutes.

You need a Voidhash project and its publishable key (vh_pk_…). Find it in Studio under Settings → API keys.

Install the SDK

The web SDK is published to pkg.voidha.sh from every change, before it is released on npm. Install the newest build:

npm install @voidhash/web@https://pkg.voidha.sh/voidhash-web/main effect@4.0.3

effect is a peer dependency, and the SDK supports effect 4.0.3, so the command pins it.

For production, pin a build, so every deploy installs the same SDK. The version of an installed build ends with -g and its short commit hash, as in 0.0.1-alpha.1-g83f7412; install that build with https://pkg.voidha.sh/voidhash-web/83f7412, using your hash. Your lockfile then records exactly that build.

Not on npm yet

npm install @voidhash/web installs an early preview that predates this guide. Install from pkg.voidha.sh until the SDK is released on npm.

Add the client

Pick the setup that matches your site. Each one records page views, including client-side route changes, and campaign attribution as soon as the client initializes.

Create the client in a module that runs in the browser, and initialize it once per page:

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

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

void voidhash.initialize();

Import this module from your page's entry script. Page views of navigations made with history.pushState, history.replaceState, or the back and forward buttons are recorded automatically, so a single-page app needs no router integration.

Wrap your app in the provider:

src/main.tsx
import { VoidhashProvider } from "@voidhash/web/react";
import { createRoot } from "react-dom/client";

import { App } from "./App";

createRoot(document.getElementById("root")!).render(
  <VoidhashProvider config={{ publishableKey: import.meta.env.VITE_VOIDHASH_KEY }}>
    <App />
  </VoidhashProvider>,
);

The provider creates the client and initializes it when it mounts. Read it anywhere below with the hooks:

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

export function UpgradeButton() {
  const { track } = useAnalytics();
  return <button onClick={() => void track("upgrade_clicked", { plan: "yearly" })}>Upgrade</button>;
}

React Router, TanStack Router, and any other router built on the History API get page views without extra code.

In the App Router, put the provider in a client component of your own, so you can pass options that are functions, such as onDiagnostic:

app/providers.tsx
"use client";

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

export function Providers({ children }: { children: React.ReactNode }) {
  return (
    <VoidhashProvider config={{ publishableKey: process.env.NEXT_PUBLIC_VOIDHASH_KEY! }}>
      {children}
    </VoidhashProvider>
  );
}

Then render it from the root layout, which stays a server component:

app/layout.tsx
import { Providers } from "./providers";

export default function RootLayout({ children }: { children: React.ReactNode }) {
  return (
    <html lang="en">
      <body>
        <Providers>{children}</Providers>
      </body>
    </html>
  );
}

This is safe to server-render: the provider and hooks render on the server without touching the browser, and the client initializes after hydration. Do not call initialize() at the top level of a module, because client components also run on the server; there it rejects with an error that says it needs a browser. Navigations with next/link and useRouter are recorded as page views automatically.

The Pages Router works the same way: render VoidhashProvider in pages/_app.tsx.

Track an event

await voidhash.track("checkout_started", { plan: "yearly" });

track() queues the event and returns; it never waits for the network. Events are sent in batches, retried until Voidhash accepts them, and kept across page loads while the visitor is offline. Names that begin with $ are reserved for Voidhash's own events. See Track events.

Identify signed-in users

When a visitor signs in or signs up, call identify() with your own stable user ID. Their earlier anonymous activity, including the campaign that brought them, becomes part of that user.

await voidhash.identify(user.id, { email: user.email, name: user.name });

Use the same user ID in your mobile app, and the visitor and the app user are one person. Call signOut() when the user signs out. See Identify users.

Attribute Stripe purchases

Pass the visitor's Voidhash ID to your server when it creates a Stripe Checkout Session, and set it as client_reference_id; for a subscription, set it on the subscription's metadata too. Voidhash then credits the purchase, its renewals, and its refunds to the visitor and the campaign that brought them. See Stripe revenue.

Check that events arrive

Open your site, browse a few pages, and open Activity → Analytics events in Studio. Page views appear within a few seconds. If they do not, see Troubleshooting.

Ask for consent first

If your site needs consent before analytics, start with consent pending and pass the visitor's choice when they make it. Until then, no page views, events, or person attributes are recorded, stored, or sent, and the campaign the visitor arrived from is kept for the browser session so it is not lost to the banner. The SDK still keeps its anonymous ID in the browser and can evaluate feature flags and identify a signed-in user; see what consent covers.

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

See Analytics consent for wiring a cookie banner.

Next steps

  • Record page views and campaign attribution
  • Attribute Stripe revenue to visitors and campaigns
  • Send analytics through your own domain
  • Hand visitors over to your app
PreviousWeb SDKNextIdentify users

On this page

Install the SDKAdd the clientTrack an eventIdentify signed-in usersAttribute Stripe purchasesCheck that events arriveAsk for consent firstNext steps