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

Sell on the web

Run web checkouts with your own payment provider, and credit each purchase to the visitor who bought.

Your app and your payment provider run the checkout. Your server chooses the price, by currency, region or pricing experiment, creates the checkout at the provider and sends the buyer to it. Voidhash learns what was sold afterwards, from Stripe's events or from your backend's reports, and grants the Voidhash product the sold price is mapped to. Any number of prices can map to one product, in each environment.

The one thing Voidhash needs from you is the buyer's distinct ID on the payment. With it, the purchase, its renewals and its refunds are credited to that person and to the campaign that brought them.

Send the distinct ID to your server

getDistinctId() returns the anonymous visitor's ID, or your user ID once you called identify(). Send it with your checkout request:

pricing.ts
import { voidhash } from "./lib/voidhash";

export async function buy(plan: string) {
  // Resolves at once when the client has started; before that, there is no ID yet.
  await voidhash.initialize();

  const response = await fetch("/api/checkout", {
    body: JSON.stringify({ distinctId: voidhash.getDistinctId(), plan }),
    headers: { "content-type": "application/json" },
    method: "POST",
  });
  const { url } = await response.json();
  window.location.assign(url);
}

A signed-in user's distinct ID is your own user ID, so your server often has it already. Call identify() before the checkout, so the browser's ID matches it.

A visitor who buys before signing up is fine too. The purchase lands on the anonymous visitor, and when they later sign up and you call identify(), it moves to the user with the rest of their history.

Put it on the payment

  • Stripe: set metadata.voidhash_distinct_id on the Checkout Session, and for a subscription subscription_data.metadata.voidhash_distinct_id too, so its renewals carry it. Or set it on the Stripe customer's metadata, which covers all of that customer's later payments. See Stripe revenue.
  • Another provider: store the distinct ID with your order, for example in the checkout's custom data, and report the payment with it as distinctId once the provider says it was paid. See Sell on your website.

Stripe Payment Links, the pricing table and the Buy Button cannot carry a distinct ID for each buyer. Their purchases are recorded on a placeholder person for the Stripe customer, and move to your user only from the customer's next event after your backend sets its voidhash_distinct_id. To tie anonymous visitors to their purchases, create Checkout Sessions on your server.

Test it

Use the test publishable key (vh_pk_test_…) on your staging site, and create its checkouts in Stripe test mode or your provider's sandbox, reported with a test secret key. Test purchases appear under Test in Studio and never unlock access in production. See Environments.

Example

The web example sends the distinct ID to a stand-in checkout endpoint of your own backend.

PreviousPage views and attributionNextStripe revenue

On this page

Send the distinct ID to your serverPut it on the paymentTest itExample