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
Web SDK
Web quickstart
Script tag
Identify users
Track events
Privacy and consent
Page views and attribution
Sell on the web
Stripe revenue
Web-to-app handoff
First-party proxy
Tracking domain
Feature flags
Troubleshooting

Web-to-app handoff

Keep one person from the ad click on your website to the purchase and use in your app.

In a web-to-app funnel, a visitor arrives from an ad on your website, signs up and often pays there, then installs your app and keeps using it. Voidhash keeps that visitor one person across both: the campaign that brought them, the Stripe purchase, and what they do in the app all belong to the same person, and the access they bought on the web works in the app.

The handoff is your user ID. Identify the visitor with it on the web, and identify the app user with the same ID after they sign in.

Install the SDK on both sides

Install the web SDK on your landing and signup pages, and a mobile SDK in your app: React Native, Swift, or Kotlin. Use the same Voidhash project for both, so they share people.

Identify on the web when the account is created

Call identify() as soon as the visitor has an account, before checkout:

Website
await voidhash.identify(user.id, { email: user.email });

The anonymous visitor, with the campaign that brought them, becomes your user.

Pay with the user's ID

Create the Stripe Checkout Session with the same user ID, so the purchase lands on the user. See Stripe revenue.

Server
await stripe.checkout.sessions.create({
  metadata: { voidhash_distinct_id: user.id },
  subscription_data: { metadata: { voidhash_distinct_id: user.id } },
  // …
});

Identify in the app after sign-in

When the user signs in to the app, identify them with the same ID:

App (React Native)
await voidhash.client.identify(user.id);

The app's anonymous install is merged into the user. Perks granted by the Stripe purchase are now available in the app, for example through useHasPerk("premium"), and the app's events and store purchases join the same person.

Make the app sign-in effortless

How the user signs in to the app is up to you; any method that ends with identify() and the same user ID completes the handoff. To keep the drop-off between web and app low:

  • Ask for the email address early in the web funnel, and use it as the app login.
  • After the web purchase, show a download page with a one-time login link or code from your own backend, so the user does not have to type a password in the app.
  • Identify in the app right after sign-in, before you check access, so the purchased perks are there on the first screen.

The order does not matter

If a user signs up in the app first and later visits your website, the same calls link them: the website's anonymous visitor is merged into the existing user when identify() runs, and the older of the two people is kept. Revenue from Stripe, the App Store, and Google Play ends up on one person.

Credit Android installs to the campaign

A link to Google Play can carry the campaign that brought the visitor, so the Android SDK credits the install to it. Build the link with playStoreUrl() when the visitor follows it, to carry their latest campaign by then:

downloadButton.addEventListener("click", () => {
  window.location.href = voidhash.playStoreUrl("com.example.app");
});

The referrer of the link holds the latest touch's utm_* parameters, click IDs, campaign_id, adset_id and ad_id, under those names, and the time of the click as touch_at, in ISO-8601 UTC. The Android SDK reads them after the install with the same rules as the web, and with that time the install does not make an old click look new: a visitor who clicked an ad months ago and follows the link today falls outside your attribution window. The result is the plain Play Store link without analytics consent, without a touch, and until initialize() resolves.

What a store link does not do

  • It carries no identity. Neither an anonymous ID nor a user ID is ever put in a link, because a person's identity carries what they bought and a shared link must not hand that to someone else. The install is a separate person until the user signs in, so the sign-in above is still what completes the handoff.
  • The App Store has no referrer. A link to the App Store cannot carry a campaign at all, so on iOS sign-in is the only handoff.

Next steps

  • Identify users
  • Stripe revenue
  • Identify customers in React Native
PreviousStripe revenueNextFirst-party proxy

On this page

Install the SDK on both sidesIdentify on the web when the account is createdPay with the user's IDIdentify in the app after sign-inMake the app sign-in effortlessThe order does not matterCredit Android installs to the campaignWhat a store link does not doNext steps