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

Connect Stripe

Connect your Stripe account to Voidhash so web purchases grant access and appear in revenue analytics.

Connecting Stripe lets Voidhash record purchases your customers make through Stripe Checkout and Stripe Billing, keep subscription state current, and grant the matching access. Your app and Stripe run the checkout: your server picks the price and creates the Checkout Session, and Voidhash learns what was sold from Stripe's events. You need the Stripe products and prices you sell to exist first.

Voidhash keeps live and test data apart. Connect live mode for production revenue, and test mode or a Stripe sandbox for staging; each mode has its own key and event destination. You can start with test mode only and add live credentials later. See Live and test mode.

Create an API key

In the Stripe Dashboard, open Developers → API keys and create a restricted key. Voidhash only reads from Stripe, so grant Read access to:

  • Balance, for the Stripe fee of each payment
  • Charges, which also covers refunds
  • Checkout Sessions
  • Customers, for the Voidhash distinct ID in a customer's metadata
  • Disputes
  • Events, for the details of each event Voidhash receives
  • Invoices, which also covers invoice payments
  • Payment Intents
  • Prices, to list your prices for import and check each price you connect
  • Products, for the product names among them
  • Subscriptions

A secret key also works, but a restricted key limits what Voidhash can do.

Add the provider in Voidhash

In Studio, open Settings → Payment providers → Stripe. Enter your Stripe account ID (acct_…) and the API key for each mode, and copy the Webhook endpoint URL.

Add an event destination

In the Stripe Dashboard, open Developers → Webhooks and add a destination:

  1. Choose events from Your account.
  2. Under the payload style, choose Thin.
  3. Select these events:
    • v1.checkout.session.completed
    • v1.checkout.session.async_payment_succeeded
    • v1.invoice.paid
    • v1.invoice.payment_failed
    • v1.customer.subscription.updated
    • v1.customer.subscription.deleted
    • v1.charge.refunded
    • v1.charge.refund.updated
    • v1.charge.dispute.closed
  4. Choose Webhook endpoint and paste the URL from Studio.

Copy the destination's signing secret (whsec_…) into Studio for the same mode, then save and enable the provider. Repeat for the other mode.

Thin events carry no object data, so they don't depend on your account's API version. Voidhash reads each object from Stripe when the event arrives.

Connect products

In Products, open Import products. It lists the active prices of your active Stripe products in each mode you connected, and marks the test mode ones. Link each price to the Voidhash product it grants, or create the product from it. You can also open a Voidhash product and add a Stripe product ID (prod_…) and price ID (price_…) yourself, for example for an archived price that existing subscriptions still renew on.

Connect every price you sell the product under, for example one per currency, region or pricing experiment: each of them grants the same product. Live and test mode have different IDs, so connect both the live prices and the test prices to the same Voidhash product. When you save a price, Voidhash reads from Stripe whether it is live or test. Purchases of a price that isn't connected yet are held and applied once you connect it.

Live and test mode

Purchases in Stripe test mode are recorded in the test environment. They appear under Test in Studio, reach test webhooks, and unlock access only for test API keys. They never unlock access in production, because anyone who reaches a test checkout pays nothing.

Set up each deployment with the keys of the same mode:

  • Staging, with your Stripe test keys: Voidhash test keys (vh_pk_test_… on the website, vh_sk_test_… on the server).
  • Production, with your Stripe live keys: Voidhash production keys (vh_pk_… and vh_sk_…).

Import your Stripe history

Webhooks only report what happens after you connect Stripe. To bring in the purchases your customers made before that, open the provider's Import history tab in Studio and start an import for live or test mode. It uses the saved API key of that mode. A restricted key also needs read access to Invoices, Checkout Sessions, Refunds, Disputes, and Subscriptions; the import checks this first and stops right away, naming what the key can't read.

The import reads what Stripe still holds for everything created before the import started:

  • paid subscription invoices, as subscription purchases and renewals
  • paid one-time Checkout Sessions, and subscription Checkout Sessions, which link their customer to the buyer they name
  • refunds and lost disputes
  • cancellations, ended subscriptions, and subscriptions whose payment is failing now

Each is recorded as of when it happened, the same way its webhook would have recorded it, so access and revenue analytics reflect your history. Amounts in other currencies are converted to USD at the exchange rate of the day they were paid, for payments since 2010. Changes Stripe keeps no record of, such as a cancellation that was later undone, aren't imported. Stripe records no closing date for a dispute, so a lost dispute is dated when it was opened.

Things to know:

  • Connect your Stripe prices to products first. Purchases of a price that isn't connected wait and are imported once you connect it; the tab lists those prices.
  • Imported events don't send your project's webhooks, since your systems already handled them when they happened.
  • The import runs in the background, and you can leave the page. One import per mode runs at a time.
  • Running it again is safe: anything already recorded, by a webhook or an earlier import, is skipped. Run it again if an import failed or you canceled it.
  • An object the import can't read or record is counted as failed, and the import carries on. If Stripe is unavailable or rate limits the import, it waits and resumes.
  • Past purchases rarely carry a Voidhash distinct ID. The import also reads it from the Stripe customer's metadata; without one, a purchase is attributed to a person tied to the Stripe customer, as described below.

Identify the buyer

Tell Voidhash which of your users made a purchase by passing their Voidhash distinct ID as voidhash_distinct_id in Stripe metadata. In the browser, the web SDK's getDistinctId() gives it: the anonymous visitor's ID, or your user ID once you called identify(). For a signed-in user it is your own user ID, so your server often has it already. Metadata takes any characters, so anonymous IDs and email addresses pass through unchanged. Voidhash looks for it in this order and uses the first it finds:

  1. Checkout Session: metadata.voidhash_distinct_id. For a subscription, also set subscription_data.metadata.voidhash_distinct_id: its payments are recorded from its invoices, which carry the subscription's metadata. A subscription session links the Stripe customer to its buyer when it completes, if the customer belongs to nobody yet or to a stand-in an earlier invoice created: a named user takes over the stand-in, and an anonymous visitor receives the stand-in's subscriptions with their access, while the payments already recorded stay with the stand-in. A customer that already belongs to a person stays with them.
  2. Subscription: metadata.voidhash_distinct_id on the subscription. Stripe copies it to each invoice when it issues the invoice, so it only applies to invoices issued after you set it.
  3. Customer: metadata.voidhash_distinct_id on the Stripe customer. It applies from the customer's next event, and to recorded purchases once you link existing customers.

Voidhash does not read client_reference_id, so you can use it for your own order or cart IDs.

const session = await stripe.checkout.sessions.create({
  line_items: [{ price: priceId, quantity: 1 }],
  metadata: { voidhash_distinct_id: distinctId },
  mode: "subscription",
  subscription_data: { metadata: { voidhash_distinct_id: distinctId } },
  success_url: "https://example.com/welcome",
});

Stripe revenue shows the browser and server code end to end.

If you create subscriptions through the API for a signed-in user, set the distinct ID on the customer. It's the one place you can add it later, and it covers every subscription and invoice of the customer:

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

Without a distinct ID anywhere, Voidhash attributes the purchase to a person tied to the Stripe customer, and later purchases by that customer go to the same person. Once the customer names a user, that person is merged into the user: their purchases, subscriptions, and access become the user's, alongside anything the user already bought. Voidhash reads the customer when an event for it arrives, so a change to its metadata applies from the customer's next event.

Link existing customers

To attribute purchases that are already recorded:

  1. Set metadata.voidhash_distinct_id on each existing Stripe customer, for example with a script that maps your users to their Stripe customer IDs.
  2. Check that your API key can read Customers.
  3. Import your history again. Purchases already recorded aren't counted twice; their customers are linked to the users their metadata names.

Customers without a recorded purchase need nothing more: their next purchase uses the metadata.

A one-time Checkout Session creates no Stripe customer unless you set customer_creation to always. Without a distinct ID, such a purchase is still recorded, but for an anonymous person of its own that no user can be matched to later, so always pass the distinct ID.

Payment Links, the pricing table and the Buy Button

Payment Links, the pricing table and the Buy Button cannot carry metadata for each buyer. Purchases made through them are still recorded, but on a placeholder person for the Stripe customer. Once your backend sets the customer's voidhash_distinct_id to your user ID, for example after sign-up, the customer's next event, such as a renewal, moves them to that user. To tie anonymous visitors to purchases, for example for ad attribution, create Checkout Sessions on your server.

What a purchase grants

  • A subscription grants one Voidhash product: its first price that is connected to a product. Other prices on the subscription, such as add-ons, count toward its revenue but grant nothing.
  • A one-time Checkout Session grants every connected product among its line items, each as a purchase of its own. A product's revenue is what its line items cost, with quantity, discounts, and tax. Line items that aren't connected, such as shipping or fees, count toward the revenue of the first connected product. A session that is free with a discount grants its products too.
  • When a subscription switches to another connected price, the new product is granted as soon as Stripe applies the switch, whether Stripe invoices it right away or with the next renewal. A switch scheduled for later, for example with a subscription schedule, is granted when it takes effect. An invoice drafted before the switch and paid after it, for example after Stripe retried its payment, renews the subscription on the new product.

How Stripe events affect access

  • A paid subscription invoice starts or renews the subscription.
  • A failed renewal payment keeps access until Stripe's next retry. If the last retry fails, access ends then, whether Stripe cancels the subscription, marks it unpaid, or leaves it past due. If the subscription is still active and the invoice is paid later, access returns.
  • Canceling at the end of the period turns off auto-renewal; access lasts until the period ends, even when the customer cancels while that period's payment is still pending. Canceling immediately ends access.
  • Fully refunding a one-time payment removes the access of every product it bought. Refunding a subscription payment records the refund but does not end the subscription; cancel the subscription in Stripe to end access.
  • Partial refunds and lost disputes are deducted from revenue. A partial refund of a payment for several products is shared among them in proportion to what each cost.
PreviousConnect Google PlayNextRecord payments from other sources

On this page

Create an API keyAdd the provider in VoidhashAdd an event destinationConnect productsLive and test modeImport your Stripe historyIdentify the buyerLink existing customersPayment Links, the pricing table and the Buy ButtonWhat a purchase grantsHow Stripe events affect access