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. The SDK's part is to tell Stripe who the buyer is. Give each purchase the visitor's Voidhash ID, and Voidhash puts the purchase on that person, so their revenue, their renewals, and their refunds are credited to the campaign, ad set, and ad that first brought them.
Before you start
- Connect Stripe to your project, and connect your Stripe prices to Voidhash products.
- Install the web SDK on the pages your buyers visit, including the landing pages your ads link to.
Choose where to put the ID
Voidhash reads the buyer's ID from the first of these places that has one: the Checkout Session's
client_reference_id, then the session's or subscription's metadata.voidhash_distinct_id, then
the Stripe customer's metadata.voidhash_distinct_id. Pick the one that matches how you sell.
| Where | Use it when |
|---|---|
Checkout Session client_reference_id | Your server creates a Checkout Session. The simplest option. |
Checkout Session or subscription metadata.voidhash_distinct_id | You do not set client_reference_id. When you set it, it must be the Voidhash ID, because it wins. |
Stripe customer metadata.voidhash_distinct_id | You keep a Stripe customer per user, create subscriptions through the API, or need to link past purchases. |
For a subscription, also put the ID on the subscription with
subscription_data.metadata.voidhash_distinct_id. A subscription's payments, the first one
included, are recorded from its invoices, and the subscription's metadata reaches each of them.
Without it, the session's ID links the Stripe customer when the checkout completes, so the invoices
follow it; see Without a server for when that is not enough. Setting the ID on
the customer covers every subscription and invoice of that customer at once.
Send the ID from the browser
Read the visitor's ID and send it with your checkout request. When the visitor is signed in, call
identify() first, so the purchase lands on your user rather than the anonymous visitor.
import { voidhash } from "./lib/voidhash";
export async function startCheckout(plan: string) {
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 it from useVoidhash().distinctId.
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.
Create the Checkout Session
Pass the ID to Stripe on your server. With client_reference_id:
import Stripe from "stripe";
const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!);
export async function createCheckout(input: { distinctId: string; priceId: string }) {
const session = await stripe.checkout.sessions.create({
client_reference_id: input.distinctId,
line_items: [{ price: input.priceId, quantity: 1 }],
mode: "subscription",
subscription_data: {
metadata: { voidhash_distinct_id: input.distinctId },
},
success_url: "https://example.com/welcome",
});
return session.url;
}Or with metadata, leaving client_reference_id free for your own use:
await stripe.checkout.sessions.create({
line_items: [{ price: input.priceId, quantity: 1 }],
metadata: { voidhash_distinct_id: input.distinctId },
mode: "subscription",
subscription_data: {
metadata: { voidhash_distinct_id: input.distinctId },
},
success_url: "https://example.com/welcome",
});For a one-time payment (mode: "payment"), client_reference_id or the session's metadata is all
you need.
In Next.js
A route handler that the browser code above calls:
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({
client_reference_id: distinctId,
line_items: [{ price: PRICES[plan], quantity: 1 }],
mode: "subscription",
subscription_data: { metadata: { voidhash_distinct_id: distinctId } },
success_url: `${new URL(request.url).origin}/welcome`,
});
return Response.json({ url: session.url });
}If your server already knows the signed-in user, use their user ID, the one you pass to
identify(), instead of the ID from the request.
Put the ID on the Stripe customer
If you keep one Stripe customer per user, set the ID on the customer when you create it:
const customer = await stripe.customers.create({
email: user.email,
metadata: { voidhash_distinct_id: user.id },
});Or add it to an existing customer:
await stripe.customers.update(customerId, {
metadata: { voidhash_distinct_id: user.id },
});The customer's ID applies to every purchase of that customer that carries no ID of its own, including subscriptions created through the API, purchases in the customer portal, and past purchases once you import your Stripe history again. A change applies from the customer's next event.
Use your user ID here, not an anonymous ID: a customer outlives a browser.
Without a server
Stripe Payment Links and the pricing table take a client_reference_id too, as a URL parameter and
an attribute. They accept only letters, digits, dashes, and underscores and drop anything else, so
use getStripeClientReferenceId(). It returns an anonymous visitor's ID in a form they accept,
vh_anon_…, which Voidhash reads back, and a user ID as is.
const reference = voidhash.getStripeClientReferenceId();
const link = new URL("https://buy.stripe.com/…");
if (reference) link.searchParams.set("client_reference_id", reference);
document.querySelector<HTMLAnchorElement>("#buy")!.href = link.toString();const { client } = useVoidhash();
const reference = client.getStripeClientReferenceId();
<stripe-pricing-table
pricing-table-id="prctbl_…"
publishable-key="pk_live_…"
client-reference-id={reference}
/>;For a user ID Stripe would drop, such as an email address, getStripeClientReferenceId() returns
undefined; use a Checkout Session on a server for those users.
A subscription bought this way is linked through the Stripe customer when its checkout completes,
and its invoices, renewals included, then follow the buyer. Stripe can deliver the first invoice
before the checkout, or both at once; the subscription reaches the buyer in either case, with its
access and renewals. For a signed-in user, the first payment is theirs as well. For an anonymous
visitor, it stays recorded on a placeholder for the Stripe customer, so revenue reports do not count
it toward the visitor's campaign. A Stripe customer that already belongs to a person is never moved
by a subscription checkout. To attribute every payment from the first one, create the Checkout
Session on a server with subscription_data.metadata.
Check the result
After a test purchase, open the person in Studio: the purchase appears in their history, next to their page views and campaign traits. Ads reports count the purchase toward the campaign in the person's first touch.
To act on the purchase on your server, check access with the Node.js library, using the same ID:
const hasPremium = await voidhash.entitlements.hasActivePerk({
distinctId: user.id,
perkSlug: "premium",
});How revenue is credited
- A purchase belongs to the person behind the ID it carries. Without an ID anywhere, Voidhash records it for a person tied to the Stripe customer, and merges that person into your user once the customer's metadata names them.
- A person's acquisition is their first touch, the
$initial_*traits the SDK recorded on their first campaign visit. Every purchase, renewal, and refund of the person counts toward that campaign, ad set, and ad. - When the same user also signs in to your app, purchases from the App Store and Google Play land on the same person. See Keep web and app users the same person.
See Stripe setup for what each purchase grants and how renewals, upgrades, and refunds are recorded.