Unlock the app after a web purchase
Sell a subscription with Stripe Checkout on your website and unlock it in your iOS and Android apps.
A person who buys on your website should find the purchase unlocked when they sign in to your app.
The link between the two is your user ID: the Stripe purchase carries it as the distinct ID, and the
app passes the same ID to identify. Voidhash then grants the perk to one person on every platform.
Before you start
- Connect Stripe (Help center), including its event destination, for live and test mode.
- Connect each Stripe price to a product that grants your perk, such as
premium. - Install the web SDK on your website and a mobile SDK in your app, both on the same Voidhash project.
Set up
Identify the buyer on the website
Call identify() once the visitor has an account, before checkout. Their anonymous visit, and the
campaign that brought them, become your user.
await voidhash.identify("user_123", { email: "ada@example.com" });A visitor can also pay before signing up: send voidhash.getDistinctId() instead, and the purchase
moves to the user when you call identify() later. See Sell on the web.
Create the Checkout Session with the distinct ID
On your server, put the distinct ID in the session's metadata and, for a subscription, in
subscription_data.metadata, so renewals and refunds reach the same person:
import Stripe from "stripe";
const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!);
export async function createCheckout(userId: string) {
const session = await stripe.checkout.sessions.create({
mode: "subscription",
line_items: [{ price: "price_123", quantity: 1 }],
metadata: { voidhash_distinct_id: userId },
subscription_data: { metadata: { voidhash_distinct_id: userId } },
success_url: "https://example.com/get-the-app",
});
return session.url;
}Voidhash grants the perk when Stripe reports the paid invoice. Check the person in People: the
subscription and the premium perk appear on user_123.
Send the buyer to the app
On the success page, link to the App Store and Google Play. Store links carry no identity, so the app
learns who the person is only when they sign in. Make that easy: use the email from checkout as the
login, or send a one-time sign-in link from your backend. To be told when a purchase lands, subscribe
a webhook to subscription.created.
Identify in the app and check the perk
After sign-in, identify with the same user ID, then check the perk. identify refreshes the person,
so the web purchase is there on the first screen.
try await voidhash.identify(externalUserId: "user_123")
let hasPremium = try await voidhash.getCurrentPerson()?.hasActivePerk("premium") ?? falsevoidhash.identify(externalUserId = "user_123")
val hasPremium = voidhash.getCurrentPerson()?.hasActivePerk("premium") == trueawait voidhash.client.identify("user_123");
const result = await voidhash.client.hasPerk("premium");If the person is already signed in to the app when they buy on the web, the app sees the purchase
on its first read after the cached person turns 5 minutes old. To check at once, for example when the app returns to the
foreground, read the person with forceFetch; see the entitlements page for
Swift, Kotlin or
React Native.
When the app doesn't unlock
| Symptom | Cause | Fix |
|---|---|---|
| The purchase is on a person with no user ID | No voidhash_distinct_id reached Stripe, so it landed on a stand-in for the Stripe customer. | Set voidhash_distinct_id on the Stripe customer; the stand-in merges into the user on the customer's next event. See Where Voidhash reads the ID. |
| The purchase came from a Payment Link, pricing table or Buy Button | These can't carry a distinct ID per buyer. | Create Checkout Sessions on your server, or set the customer's metadata after sign-up. |
| The purchase is recorded but grants nothing | The price isn't connected to a product. | Connect it; the purchase is applied once you do. |
| Nothing is recorded | Stripe's event destination is missing for this mode. | Add it; see Connect Stripe (Help center). |
| A test-mode purchase doesn't unlock the app | Stripe test mode records into test, which never unlocks production. | Test with an app build that uses the test publishable key. |
| The app shows the old answer | The person is cached. | Read with forceFetch, or wait for the next refresh. |
Ask why with the MCP tool explain_person_access or voidhash person why user_123 premium: each
names the first broken link from perk to purchase. See
Why does this person have access?.
Next steps
- Web-to-app handoff: keep the ad campaign with the person too.
- Stripe (Help center)
- Link accounts across platforms
Something wrong or missing on this page? Tell us by email or on Discord.