Page views and attribution
Record page views and sessions, and attribute visitors and revenue to campaigns and ads.
The web SDK records which pages visitors see and which campaign or ad brought them to your site. Combined with your Stripe payments, this tells you the trials, conversions, revenue and refunds each campaign, ad set and ad produced.
Page views
The SDK records a $pageview event when the page loads and after every client-side navigation
that changes the path, through history.pushState, history.replaceState or the back and forward
buttons. Changes to the hash do not count, and a navigation that only changes the query string is
not a new page view.
This covers single-page apps without any router code: Next.js (App Router and Pages Router), React Router, TanStack Router, Vue Router, SvelteKit, and any router built on the History API.
Every event carries the page's origin, path and title, the referrer's origin and path, and the screen and viewport size. The query string is not recorded, except for the campaign parameters below.
To record page views yourself, turn the automatic ones off and call page():
const voidhash = createVoidhashClient({
analytics: { capturePageViews: false },
publishableKey: "vh_pk_…",
});
await voidhash.page("Pricing", { experiment: "annual-first" });Campaign attribution is recorded either way.
page() used to send a custom event named page. It now sends $pageview, the same event the
SDK records automatically. If your app already calls page() on every route change, set
capturePageViews: false, or every navigation is counted twice.
$pageview is a built-in event that self-hosted installs do not store by default. Turn it on in
the project's event settings, or page views are discarded when they arrive.
Create one client per project on a page. Two clients for the same project each record their own page views, so every page would be counted twice.
Sessions
Every event carries a session id. A session ends after 30 minutes without events, the same as on
iOS, Android and React Native. All tabs of your site share the session, and reset() starts a new
one.
Campaign attribution
A visit whose URL has any of these parameters is a campaign touch:
| Parameter | Source |
|---|---|
utm_source, utm_medium, utm_campaign, utm_term, utm_content, utm_id | Any campaign |
fbclid | Meta ad clicks |
gclid | Google Ads clicks |
campaign_id, adset_id, ad_id | Ad ids you add to links |
The SDK writes each touch to the visitor's person profile:
- The first touch is kept forever, as the
$initial_…traits. - Every new touch replaces the
$latest_…traits.
| Trait suffix | Value |
|---|---|
utm_*, fbclid, gclid | The URL parameters, unchanged |
ad_network | meta for fbclid or a Facebook, Instagram or Meta utm_source; google for gclid; otherwise the lowercased utm_source |
ad_campaign_id | campaign_id, or utm_id when there is no campaign_id |
ad_adset_id | adset_id |
ad_id | ad_id |
landing_path | The path of the page the visitor landed on, without the query string |
referrer_origin | The origin of the referring page |
touch_at | When the touch happened, in ISO-8601 UTC |
For example, $initial_ad_campaign_id is the campaign of the visitor's first ad click and
$latest_utm_source the source of their most recent campaign visit. The page view of the touch
carries the same values as $utm_source, $ad_campaign_id and so on.
Reloading the page or navigating within your site with the same campaign parameters does not count
as a new touch. Each touch writes every trait, with null for the values it does not have, so the
first-touch and latest-touch traits always describe a single visit and never mix values from two.
The page view of a touch carries only the values it has.
When a visitor signs in, identify() links their anonymous profile, including its campaign traits,
to the user.
Meta ads
Add these URL parameters to your ads. In Ads Manager, set them under Tracking > URL parameters at the ad level:
utm_source=facebook&utm_medium=paid_social&utm_campaign={{campaign.name}}&utm_term={{adset.name}}&utm_content={{ad.name}}&campaign_id={{campaign.id}}&adset_id={{adset.id}}&ad_id={{ad.id}}Meta fills in the {{…}} values when the ad is clicked, and adds fbclid itself. The ids give
exact attribution to a campaign, ad set and ad even after you rename them.
The SDK also stores Meta's click and browser ids on the person, as $fbc and $fbp:
$fbccomes from the Meta pixel's_fbccookie when it is present and less than 90 days old. Otherwise the SDK builds it from thefbclidof the visit, in Meta'sfb.1.<timestamp>.<fbclid>format.$fbpcomes from the Meta pixel's_fbpcookie, when your site uses the pixel.
The SDK reads these cookies but never sets any.
Attribute Stripe payments
To credit revenue, renewals, and refunds to the campaign that brought the visitor, pass the visitor's Voidhash ID to Stripe when your server creates the Checkout Session or customer. See Stripe revenue.
Bots and crawlers
Events from well-known crawlers, link-preview fetchers, uptime monitors and headless browsers are discarded when they arrive, so they do not count as page views, sessions or campaign touches.