VoidhashDocs
DevelopersHelp centerGuidesAPI reference
Dashboard
Get started
Introduction
Quickstart
How Voidhash works
Glossary
Versions and releases
Concepts
Teams, projects and environments
API keys
Products and perks
Offers
Subscription lifecycle
People and identity
Events
Features
Paywalls in your app
Feature flags and A/B tests
Paywall components
Push notifications
Ad attribution in your app
Client SDKs
React Native
Swift
Kotlin
Web
Your backend
Node.js
PHP
Go
Rust
Other languages
Payment sources
Forward store notifications
Developer tools
CLI
MCP server
Query analytics with VoidQL
Web SDK
Web quickstart
Script tag
Identify users
Evaluate feature flags
Track
Track events
Page views and attribution
Privacy
Privacy and consent
Browser storage
Connect a consent tool
Keep data out of analytics
Revenue and attribution
Sell on the web
Stripe revenue
Web-to-app handoff
Delivery
First-party proxy
Tracking domain
Reference
Web SDK reference
Errors
Troubleshooting

Keep data out of analytics

Keep private pages, addresses and properties out of what the web SDK records.

The web SDK gives you three ways to keep data out of analytics: sanitizeUrl rewrites or hides page addresses, beforeSend edits or drops events, and pauseAnalytics() stops recording for a while. All three apply before anything is queued or stored.

Keep pages out by address

Some addresses carry data that must not reach Voidhash: a health result in /app/results/42, a customer ID in an admin page, a token in an invitation link. analytics.sanitizeUrl sees each address before it is recorded and returns the address to record, or null to keep it out:

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

const voidhash = createVoidhashClient({
  analytics: {
    sanitizeUrl: (url) => {
      // Pages with health data stay out entirely.
      if (url.pathname.startsWith("/app/")) return null;
      // Admin and invitation pages are recorded without the ID or token.
      url.pathname = url.pathname
        .replace(/^\/admin\/customers\/[^/]+/, "/admin/customers/:id")
        .replace(/^\/invite\/[^/]+/, "/invite/:token");
      return url;
    },
  },
  publishableKey: "vh_pk_...",
});

On a page it returns null for:

  • No page view, page leave, outbound click or campaign touch is recorded.
  • Events you track there carry no address and no title. To record nothing at all, also drop them in beforeSend.
  • Feature flags, identify() and delivery of other pages' events keep working.

It also runs on the referrer, which can be another site's address. A rewritten address replaces the real one everywhere, including a campaign's landing path, and the campaign is read from its query, so change the URL you get rather than build a new one. It may also return a string, read relative to the page's address. A function that throws keeps the address out and reports a SANITIZE_URL_FAILED diagnostic.

sanitizeUrl does not cover data you pass yourself. Keep personal data out of track() and setPersonAttributes(), and out of the titles of pages you keep, or remove it in beforeSend.

In the Next.js App Router, pass function options to VoidhashProvider from a "use client" module of your own.

Edit or drop events

analytics.beforeSend sees every event before it is queued: page views, page leaves, outbound clicks, $set, $sign_out and the events you track. Return the event, changed or not, or null to drop it:

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

const voidhash = createVoidhashClient({
  analytics: {
    beforeSend: (event) => {
      if (event.event === "internal_debug") return null;
      delete event.properties.coupon_code;
      return event;
    },
  },
  publishableKey: "vh_pk_...",
});
FieldContents
eventThe name, such as $pageview, $set or one you track.
propertiesYour properties and the SDK's $ properties. A $set event carries its traits in $set and $set_once.
contexturl_origin, url_path, page_title, referrer_origin, referrer_path, and screen and viewport size.
distinctId, eventId, sessionId, timestampRead only.
  • Change properties and context in place, or return a new event. Other changes are ignored.
  • Pass a list of functions to run them in order; the first null drops the event.
  • A function that throws, or returns anything but an event or null (such as a promise), drops the event and reports BEFORE_SEND_FAILED.
  • It runs once per event, when it is recorded. A dropped event never starts or extends a session.
  • Change $ properties only on purpose: Voidhash reads them, for example $platform.

identify() sends no event, so beforeSend does not see it. It sends the user ID and the traits you pass, without the page's address.

Pause recording

pauseAnalytics() stops recording at once, until resumeAnalytics():

voidhash.pauseAnalytics();
voidhash.isAnalyticsPaused(); // true
voidhash.resumeAnalytics();

While paused, nothing is recorded: page views, page leaves, outbound clicks, campaigns, events and person attributes. Unlike withdrawing consent, the pause is not stored, events queued before it are still sent, and consent, feature flags and identify() are unaffected. Resuming records nothing by itself; the next navigation records its page view, or call page(). It works before initialize(), so even the first page view can be skipped.

The SDK records a page view just after the History API call, before your router renders the new page. To keep a page out with a pause, pause before the navigation that opens it, such as in your router's navigation hook, and resume after the visitor leaves it. sanitizeUrl has no such timing and is usually simpler. In React, useAnalytics() returns pauseAnalytics and resumeAnalytics.

Next steps

  • Privacy and consent
  • Track events
  • Troubleshooting: a private page shows up

Something wrong or missing on this page? Tell us by email or on Discord.

PreviousConnect a consent toolNextSell on the web

On this page

Keep pages out by addressEdit or drop eventsPause recordingNext steps