Your Apple keys are account-level — set them up once
Here's the part that saves you time: your Apple credentials — the issuer ID, two API key IDs, and two .p8 files — authenticate every app under your Apple Developer account. You enter them a single time. After that, connecting an app needs only its Bundle ID and App Store ID, and Crossdeck pre-fills both from onboarding. One app or ten, you set up keys once.
Two keys, two jobs: the App Store Server API key handles transactions and subscription status; the App Store Connect API key reads your product catalog. Apple splits these across two APIs, so your account holds one of each. And as with Stripe, the line of code at purchase time is the part that decides whether a sale finds the right person — that's the piece to get right.
Keys once, app in seconds, one option at purchase
Three steps, and only the last one is code:
1 · Set up your account (once). In the dashboard, enter the issuer ID, both API key IDs, and both .p8 files. Crossdeck verifies them live with Apple and stores them as your account. Keys live in Google Cloud Secret Manager — never in Firestore, never in a bundle.
2 · Connect an app (every time after). Confirm the app's Bundle ID and App Store ID, then paste that app's appleWebhook/{projectId} URL into App Store Connect as both the Production and Sandbox V2 URL. Each app has its own webhook URL.
3 · Tell Crossdeck who's buying. At purchase time, attach the current identity's token to the StoreKit purchase:
// the UUID bound to the user you identify()'d — never nil
let token = Crossdeck.appAccountTokenForCurrentIdentity()
let result = try await product.purchase(options: [
.appAccountToken(token)
])
One UUID, baked into the transaction
appAccountTokenForCurrentIdentity() returns the UUID bound to the user you passed to identify() — and it never returns nil. If someone buys before they've signed in, it mints a stable UUID, persists it on the device, and returns that; when they identify later, the purchase stitches onto them. StoreKit bakes the UUID into the transaction, Apple's server notification carries it back, and Crossdeck reads it to attach the subscription to that exact customer.
Leave the option off and the App Store purchase still arrives — but tokenless, so it orphans as a rail-only customer that has to be reconciled by hand later. And a worry this design removes: shared keys never leak revenue between your apps. Each app's webhook is its own URL, and Apple's SignedDataVerifier binds every transaction to that app's own Bundle ID — a transaction meant for another app is rejected outright.
The sandbox purchase lands on the right customer
Run a sandbox purchase. The subscription appears on the customer you identified, and the entitlement it grants is live — ready for your gate to read, the same way Stripe's did.
user_847's App Store subscription, matched by appAccountToken — entitlement live. No tokenless orphan.