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

Stripe revenue

Tie Stripe purchases to the web visitor, so revenue is credited to the campaign that brought them.

Voidhash records Stripe purchases from Stripe itself: subscriptions, renewals, one-time payments, refunds and disputes arrive through your Stripe connection, whether or not the SDK is on the page. Add the visitor's distinct ID to the purchase, and Voidhash puts it on that person and credits its revenue to the campaign, ad set and ad that brought them.

Before you start

  • Connect Stripe (Help center) to your project and map your Stripe prices to products.
  • Install the web SDK on the pages buyers visit, including ad landing pages.

Where Voidhash reads the ID

Put the distinct ID in Stripe metadata as voidhash_distinct_id. Voidhash reads the first of these that has one:

WhereUse it when
Checkout Session metadataYour server creates a Checkout Session. Always set it.
Subscription metadata, set with subscription_dataThe session starts a subscription. Its renewals carry it.
Stripe customer metadataYou keep one Stripe customer per user, create subscriptions through the API, or link past purchases.

Voidhash does not read client_reference_id; keep it for your own order or cart ID.

Send the ID from the browser

Read the ID with getDistinctId() and send it with your checkout request. When the visitor is signed in, call identify() first, so the purchase lands on your user.

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

export async function startCheckout(plan: string) {
  // Resolves at once if the client has started; before that there is no ID.
  await voidhash.initialize();
  const distinctId = voidhash.getDistinctId();
  await voidhash.track("checkout_started", { plan });

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

In React, read useVoidhash().distinctId. For a signed-in user, your server can use your own user ID instead of the one from the request.

Create the Checkout Session

Put the ID on the session, and for a subscription on the subscription too:

app/api/checkout/route.ts
import Stripe from "stripe";

const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!);
const PRICES: Record<string, string> = { yearly: "price_..." };

export async function POST(request: Request) {
  const { distinctId, plan } = await request.json();
  const session = await stripe.checkout.sessions.create({
    line_items: [{ price: PRICES[plan], quantity: 1 }],
    metadata: { voidhash_distinct_id: distinctId },
    mode: "subscription",
    subscription_data: { metadata: { voidhash_distinct_id: distinctId } },
    success_url: `${new URL(request.url).origin}/welcome`,
  });
  return Response.json({ url: session.url });
}

For a one-time payment (mode: "payment"), the session's metadata is enough. Always pass it: a one-time session creates no Stripe customer unless customer_creation is always, so without an ID its purchase lands on an anonymous person no user can be matched to later. A completed subscription checkout that names a buyer takes over its customer's placeholder person.

Set both for a subscription. Its payments are recorded from invoices, which carry the subscription's metadata. Stripe can deliver the first invoice before the session completes, and without subscription_data.metadata an anonymous visitor's first payment then stays on a placeholder person for the Stripe customer.

Put the ID on the Stripe customer

If you keep one Stripe customer per user, set your user ID on the customer:

await stripe.customers.update(customerId, {
  metadata: { voidhash_distinct_id: user.id },
});

The customer's ID applies to every purchase without an ID of its own: API-created subscriptions, customer portal purchases, and past purchases when you import your Stripe history again. It applies from the customer's next event. Use your user ID, not an anonymous ID: a customer outlives a browser.

Payment Links, the pricing table and the Buy Button

These cannot carry metadata per buyer. Their purchases are recorded on a placeholder person for the Stripe customer. Once your backend sets the customer's voidhash_distinct_id, for example after sign-up, the customer's next event moves the placeholder's purchases and access to that user. Nothing links such a purchase to the browser, so no campaign gets credit for it.

Check the result

After a test purchase, open the person on Studio's People page: the purchase appears in their history next to their page views and campaign traits. Purchases in Stripe test mode are recorded in the Test environment, so a staging site uses the vh_pk_test_... key and a Stripe test key. See Test purchases (Help center).

How revenue is credited

  • A purchase belongs to the person whose ID it carries. Without one, it goes to a placeholder person for the Stripe customer, merged into your user once the customer's metadata names them.
  • A person's signup is credited to their last ad touch within your project's attribution window (7 days by default), else their last other touch. Every purchase, renewal and refund of the person counts toward that campaign. See Ad measurement.
  • When the same user signs in to your app, App Store and Google Play purchases land on the same person. See Keep web and app users the same person.

Next steps

  • Check access from your backend with Node.js, PHP, Go, Rust or over HTTP
  • Web-to-app handoff
  • Connect Stripe (Help center)

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

PreviousSell on the webNextWeb-to-app handoff

On this page

Before you startWhere Voidhash reads the IDSend the ID from the browserCreate the Checkout SessionPut the ID on the Stripe customerPayment Links, the pricing table and the Buy ButtonCheck the resultHow revenue is creditedNext steps