VoidhashDocs
DocumentationAPI reference
Dashboard
Get started
Introduction
Quickstart
Setup
Setup overview
Create a project
Environments
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
Record payments from other sources
API libraries
Node.js
PHP
Rust
Go
Resources
MCP server
Ad measurement
Meta ads
Migrate in observer mode
Delete a person's data
Web SDK
Web quickstart
Script tag
Identify users
Track events
Privacy and consent
Page views and attribution
Sell on the web
Stripe revenue
Web-to-app handoff
First-party proxy
Tracking domain
Feature flags
Troubleshooting

Script tag

Add Voidhash to a site without a JavaScript build, such as Webflow, WordPress, or plain HTML.

For a site without a bundler, the SDK also ships as one file, voidhash.global.js, that a <script> tag loads from Voidhash's CDN. It contains everything it needs, about 40 kB gzipped, and does what the npm package does: page views, campaign attribution, events, identity, and consent.

Add the snippet

Put both tags in the <head> of every page:

<script>
  window.voidhash = window.voidhash || { q: [] };
  [
    "track",
    "page",
    "identify",
    "signOut",
    "reset",
    "setAnalyticsConsent",
    "setPersonAttributes",
    "on",
  ].forEach(function (method) {
    window.voidhash[method] =
      window.voidhash[method] ||
      function () {
        window.voidhash.q.push([method, arguments]);
      };
  });
  ["getDistinctId", "getSessionId", "getFeatureVariant", "isFeatureEnabled"].forEach(
    function (method) {
      window.voidhash[method] = window.voidhash[method] || function () {};
    },
  );
</script>
<script
  async
  src="https://cdn.voidhash.com/web/latest/voidhash.global.js"
  data-publishable-key="vh_pk_…"
></script>

The first tag lets the page call window.voidhash before the file has loaded: it keeps the calls, and the methods that return a value return undefined until then. The second loads the SDK without holding up the page. When it arrives, it creates the client as window.voidhash, starts it, records the page view and the campaign the visitor came from, and then makes the kept calls, in order.

Keep the async attribute. If the file cannot be loaded, for example while the CDN is unreachable, your page renders and works as before: the kept calls are simply never made, and getDistinctId() returns undefined, so code that reads it must handle that.

Choose a version

/web/latest/voidhash.global.js is always the newest release; your site picks up a new one within minutes of it. To decide when your site updates, load a version instead, which we recommend for a production site: a release can then never change a page you have tested. A version's file never changes, so add its integrity hash, and the browser refuses to run anything else:

<script
  async
  src="https://cdn.voidhash.com/web/0.0.1-alpha.1-g83f7412/voidhash.global.js"
  integrity="sha384-…"
  crossorigin="anonymous"
  data-publishable-key="vh_pk_…"
></script>

The first line of every file names its version, so the newest one is:

curl -s https://cdn.voidhash.com/web/latest/voidhash.global.js | head -n 1
# /*! @voidhash/web 0.0.1-alpha.1-g83f7412 | MIT */

and the integrity hash of a version is:

curl -s https://cdn.voidhash.com/web/0.0.1-alpha.1-g83f7412/voidhash.global.js | openssl dgst -sha384 -binary | openssl base64 -A

You can also serve the file yourself: install the package as in the quickstart and copy dist/voidhash.global.js from it to your static files.

Options

The tag's data- attributes set the client's options:

AttributeOption
data-publishable-keypublishableKey. Required, unless window.voidhash.options sets it: without a key, the file starts no client.
data-base-urlbaseUrl, for a first-party proxy or a tracking domain.
data-analytics-consentanalyticsConsent: granted, denied, or pending.
data-analytics-consent-sourceanalyticsConsentSource: sdk or app, for a consent tool.
data-store-before-consent"false" sets storeBeforeConsent: false.
data-capture-page-views"false" turns off automatic page views.

For any other option, such as analytics.sanitizeUrl to keep private pages out, set the options in the first tag, as window.voidhash.options, and leave data-publishable-key out. The file still loads async, and the client starts with these options:

<script>
  window.voidhash = window.voidhash || { q: [] };
  window.voidhash.options = {
    publishableKey: "vh_pk_…",
    analytics: {
      sanitizeUrl: function (url) {
        return url.pathname.indexOf("/account/") === 0 ? null : url;
      },
    },
    featureFlags: { defaults: { "new-checkout": true } },
  };
  // … the rest of the snippet above …
</script>
<script async src="https://cdn.voidhash.com/web/latest/voidhash.global.js"></script>

A data- attribute on the tag wins over the same option here. If an option is wrong, the SDK does not start and reports the error to the browser console; the page is not affected.

Track events and identify visitors

Call the client from anywhere on the page, for example from a button:

<button onclick="voidhash.track('trial_started', { plan: 'pro' })">Start your free trial</button>

After a visitor signs up or signs in, identify them with your user ID:

voidhash.identify("user_42", { email: "ada@example.com" });

Before the file has loaded, only the methods the snippet lists are available. Methods that return a value, such as getDistinctId() for Stripe purchases, work once the client has started:

voidhash.on("initialized", function (event) {
  document.querySelector("#checkout").dataset.visitor = event.distinctId;
});

Ask for consent

Where your site needs consent first, as in the EU, start the client with consent pending and keep its state off the device until the visitor agrees, then pass the answer from your consent tool:

<script
  async
  src="https://cdn.voidhash.com/web/latest/voidhash.global.js"
  data-publishable-key="vh_pk_…"
  data-analytics-consent="pending"
  data-store-before-consent="false"
></script>
// In your consent tool's callback. Calls made before the file loads are kept.
voidhash.setAnalyticsConsent(analyticsAllowed ? "granted" : "denied");

With a consent tool such as Cookiebot or OneTrust, add data-analytics-consent-source="app" as well, so the tool's answer always wins over one stored on an earlier visit; see Use a consent tool. Privacy and consent explains what each answer changes.

Content Security Policy

Besides the connect-src entries in Content Security Policy, allow https://cdn.voidhash.com, or the origin you serve the file from, in script-src. The inline snippet needs a nonce or a hash in script-src, or move it into a file of its own.

PreviousWeb quickstartNextIdentify users

On this page

Add the snippetChoose a versionOptionsTrack events and identify visitorsAsk for consentContent Security Policy