Mobile purchase claims
Connect mobile purchases to FounderHQ contacts.
Tie an App Store or Google Play purchase to the person who made it, and to the account it should fund. Allow about fifteen minutes.
Before you start
Install the FounderHQ SDK (React Native, iOS, or Android) and connect a store: RevenueCat, Superwall, Apple, or Google Play.
A store purchase carries no browser session and no metadata field you can fill. It carries one UUID. The SDK mints that UUID, records what it stands for, and hands it to the store. When the store notifies FounderHQ, the UUID leads back to the buyer.
Prepare before you open the purchase sheet
const prepared = await founderhq.preparePurchase({ source: "app_store" });prepared gives you the field your store library needs:
| Store | Field to pass | Where it goes |
|---|---|---|
| App Store | prepared.appAccountToken | StoreKit purchase option appAccountToken |
| Google Play | prepared.obfuscatedExternalAccountId | BillingFlowParams.Builder.setObfuscatedAccountId |
| Superwall | prepared.appAccountToken | Superwall user attribute fhq_visitor, plus the store field above |
preparePurchase waits a moment for FounderHQ to acknowledge the context, then
returns anyway. Checkout never blocks on the network.
Whatever account is set on the SDK at this moment is frozen into the context. Changing accounts later does not move this purchase.
Report the purchase result
await founderhq.observePurchase({
prepared,
purchase: {
source: "app_store",
transactionId: transaction.id,
originalTransactionId: transaction.originalID,
},
});For Google Play, send { source: "google_play", purchaseToken, orderId }.
The source you observe must match the source you prepared.
Using RevenueCat? Wrap the purchase instead, and the SDK does both steps:
const result = await founderhq.purchaseWithRevenueCat(() =>
Purchases.purchasePackage(pkg),
);Move an existing subscription to an account
Sometimes a person already pays, and later that subscription should fund a team, family, or workspace. That is a deliberate product action, so it needs a deliberate call:
await founderhq.claimSubscription({
purchase: {
source: "app_store",
transactionId: transaction.id,
originalTransactionId: transaction.originalID,
},
confirmation: "move_future_revenue",
});A claim moves future revenue only
Payments that already happened keep the owner they had. The new owner starts at the moment FounderHQ accepts the claim. MRR moves; history does not. Refunds always follow the original payment.
Never call this from a listener, a restore, an entitlement refresh, or a sign-in. Call it from a screen where the person asked for it.
The RevenueCat identity rule
If you use RevenueCat, the FounderHQ UUID is the RevenueCat App User ID. Let
the SDK configure and log in RevenueCat, and never call
Purchases.logOut(). See Connect RevenueCat.
The same calls on every platform
| What you do | React Native | iOS | Android |
|---|---|---|---|
| Prepare | preparePurchase | preparePurchase(source:) | preparePurchase(source, completion) |
| Apply the token | pass prepared fields | purchase(_:options:) adds it | applyPurchaseContext(builder, prepared) |
| Report the result | observePurchase | observePurchase(_:prepared:) | observePurchase(purchase, prepared) |
| Wrap RevenueCat | purchaseWithRevenueCat | purchaseWithRevenueCat | revenueCatPurchaseCallback |
| Move a subscription | claimSubscription | claimSubscription(_:confirmation:) | claimSubscription(purchase, confirmation) |
From your backend
Your server can do two things with a secret key, through the Node SDK:
accountContextToken({ account, userId, idempotencyKey })issues short-lived proof that this person may act for this account. It moves no money.claimMobileSubscription({ account, userId, purchase, confirmation, idempotencyKey })moves a subscription's future revenue to an account, with the same"move_future_revenue"confirmation and the same rules as the mobile call.
Neither call creates the account or the membership. Create those first with
upsertAccount and accountMembership.
Verify
- Buy a sandbox subscription in the app.
- In FounderHQ, open Revenue. The purchase shows the buyer as a contact, and the channel that brought them.
- After a transfer claim, the next renewal counts for the account, and the earlier payments still count for the person.
Troubleshoot
The purchase has no contact
The app purchased without preparing. Call preparePurchase first, and pass the
token to the store library.
"Observed purchase source does not match its preparation"
You prepared for one store and observed another. Prepare with the same source you buy with.
A RevenueCat purchase reports no transaction
Some observer and custom-completion modes return no store transaction. The SDK will not guess from entitlements. Claim it later with a verified transaction ID or purchase token.