Purchases from the App Store

Connect the In-App Purchase key, point Apple's server notifications at Broctic, and ask whether a person is entitled — with no third party in between.

What it does

An app sells through StoreKit, and Apple tells Broctic about every purchase, renewal, refund and billing failure directly. There is no third party in between. Broctic keeps the transactions, answers the one question an app actually asks — may this person use what they paid for? — and attributes each purchase to the person and the onboarding arm that sold it, so an A/B test can say which arm earned more.

A Studio app already does the app's half: its kit/purchases.ts calls both device routes below with the publishable key it sends events with. What is left is two settings, both in App Store Connect and the console.

Set it up

1. Connect the In-App Purchase key

In App Store Connect, open Users and Access → Integrations → In-App Purchase and create a key. Then in the console, Settings → Integrations, paste its Key ID, the Issuer ID and the contents of the .p8 file. The key is sealed on the server and no route ever returns it.

It is not your App Store Connect key

App Store Connect issues two kinds of key, and the one used for analytics is the wrong one here. Apple answers the wrong kind with a bare 401, so the console marks the key as needing attention and says which tab to look in.

2. Point Apple's notifications at Broctic

In App Store Connect, open the app, then App Information → App Store Server Notifications. Paste the URL the console shows — https://api.broctic.com/v1/purchases/notifications — into both the Production and the Sandbox field, with version V2. One URL serves every app and both environments; the signed payload says which is which.

Until this is set, a renewal two years from now or a refund next week changes nothing in Broctic. Notifications are the only way those arrive.

The routes an app calls

Both take the app's publishable key (bpk_) in the x-broctic-key header — the same key as event ingest. An imported app that does not use the Studio kit calls them itself.

On launch: the entitlement

http
GET https://api.broctic.com/v1/purchases/entitlement?anonymousId=3f0c…&userId=optional
x-broctic-key: bpk_…

→ 200
{
  "entitled": true,
  "status": "billing_retry",
  "productId": "com.example.app.premium.yearly",
  "expiresAt": "2026-10-01T12:00:00Z",
  "graceExpiresAt": "2026-10-17T12:00:00Z",
  "willRenew": true,
  "ownership": "purchased",
  "originalTransactionId": "2000000…",
  "checkedAt": "2026-09-23T15:04:05Z"
}

Answered from what Apple has already reported, so it costs no call to Apple and is safe on every launch. Branch on entitled and nothing else; the rest is detail for support screens.

After a purchase: verify

http
POST https://api.broctic.com/v1/purchases/verify
Content-Type: application/json
x-broctic-key: bpk_…

{
  "signedTransaction": "eyJhbGciOiJFUzI1NiIs…",   // StoreKit 2's JWS for the transaction
  "anonymousId": "3f0c…",                          // the id the app's events carry
  "userId": "optional, only after sign-in"
}

→ 200  the same entitlement shape as above

The transaction is checked against Apple's certificate chain, so this is not trusting the device. It makes the person entitled the second they pay rather than whenever Apple's notification arrives, and it is the one moment the purchase and the person are in the same request — which is how a renewal months later is still credited to the onboarding arm that sold it.

What each status means

statusentitledMeaning
activeyesPaid and current.
billing_graceyesA renewal failed and Apple's grace period is running. The person keeps access.
billing_retryyesApple is still retrying the card. A failed payment is not a cancellation.
expirednoThe paid period ended and it did not renew.
revokednoRefunded, or removed from family sharing.
nonenoNothing Apple has told us about this person.

ownership: family_shared means the person may use the app and nobody paid for this seat — it is entitled, and it is never counted as revenue.

What to know before you trust the numbers

  • Sandbox purchases are stored, so testing is debuggable, and are never counted as revenue or sent into the funnel. Every App Store review is a sandbox purchase.
  • Revenue per onboarding arm is reported per currency and never summed across currencies.
  • The price Apple reports is what the customer was charged, before Apple's commission, tax and currency conversion. It is the right number for comparing arms and the wrong one for accounting — Apple's Sales and Trends reports are that number.
  • Apple retries a failed production notification for about six days and a sandbox one not at all. Set the notification URL before you test: a sandbox purchase whose notification was missed does not arrive again.