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:
| Where | Use it when |
|---|---|
Checkout Session metadata | Your server creates a Checkout Session. Always set it. |
Subscription metadata, set with subscription_data | The session starts a subscription. Its renewals carry it. |
Stripe customer metadata | You 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.
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:
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.