Documentation
Revenue

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:

StoreField to passWhere it goes
App Storeprepared.appAccountTokenStoreKit purchase option appAccountToken
Google Playprepared.obfuscatedExternalAccountIdBillingFlowParams.Builder.setObfuscatedAccountId
Superwallprepared.appAccountTokenSuperwall 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 doReact NativeiOSAndroid
PreparepreparePurchasepreparePurchase(source:)preparePurchase(source, completion)
Apply the tokenpass prepared fieldspurchase(_:options:) adds itapplyPurchaseContext(builder, prepared)
Report the resultobservePurchaseobservePurchase(_:prepared:)observePurchase(purchase, prepared)
Wrap RevenueCatpurchaseWithRevenueCatpurchaseWithRevenueCatrevenueCatPurchaseCallback
Move a subscriptionclaimSubscriptionclaimSubscription(_: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

  1. Buy a sandbox subscription in the app.
  2. In FounderHQ, open Revenue. The purchase shows the buyer as a contact, and the channel that brought them.
  3. 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.

AI agent or LLM? Read this page as markdown

On this page