VoidhashDocs
DevelopersHelp centerGuidesAPI reference
Dashboard
Guides
Launch
Go-live checklist
Make your first App Store purchase
Make your first Google Play purchase
Troubleshoot
Troubleshooting
Recover from incidents
SDK error codes
Purchase error codes
API error codes
FAQ
Recipes
Win back subscribers
Handle plan changes
Unlock the app after a web purchase
Link accounts across platforms
Handle refunds
Register devices for push
Migrate
Migrate to Voidhash
Migrate from RevenueCat
Migrate from Adapty
Migrate from Superwall
Run in observer mode
Privacy and hosting
Delete personal data
Self-hosting
Operate a self-hosted server

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.

signup.ts
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:

server/checkout.ts
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") ?? false
voidhash.identify(externalUserId = "user_123")
val hasPremium = voidhash.getCurrentPerson()?.hasActivePerk("premium") == true
await 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

SymptomCauseFix
The purchase is on a person with no user IDNo 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 ButtonThese 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 nothingThe price isn't connected to a product.Connect it; the purchase is applied once you do.
Nothing is recordedStripe's event destination is missing for this mode.Add it; see Connect Stripe (Help center).
A test-mode purchase doesn't unlock the appStripe 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 answerThe 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.

PreviousHandle plan changesNextLink accounts across platforms

On this page

Before you startSet upIdentify the buyer on the websiteCreate the Checkout Session with the distinct IDSend the buyer to the appIdentify in the app and check the perkWhen the app doesn't unlockNext steps