Apple App Store native IAP

Connect Apple's App Store Server Notifications V2 and App Store Server API directly, without RevenueCat in the middle. iOS-only.

When to use Apple native

The trade-offs versus the RevenueCat option.

Pick Apple native if

Your app is iOS-only (or you handle Android separately).
You already have your own backend wired up to App Store Server Notifications and want to avoid adding RevenueCat.
You don't want a third-party SaaS in the payments path.

Pick RevenueCat instead if

You ship on both iOS and Android, RevenueCat covers both in one integration.
You haven't implemented App Store Server Notifications and don't want to.
You want entitlement caching, paywall A/B testing, or any other RevenueCat-specific feature.

What you'll need from App Store Connect

Five values plus one file. Apple uses these to sign the JWT we send on every API call.

Bundle ID

Reverse-DNS identifier of your app, e.g. com.acme.app. Find under: App Store Connect → My Apps → your app → App Information.

App Apple ID

The 10-digit numeric ID Apple assigns to your app. Same page as Bundle ID, labeled 'Apple ID'.

Issuer ID

UUID for your App Store Connect API issuer. Find under: Users and Access → Integrations → App Store Connect API → Active.

Key ID

10-character identifier of the In-App Purchase key. Generated in the next step.

Private key (.p8 file)

Generated in the next step. Apple only lets you download it once, store it somewhere safe before uploading to us.

Environment

Production for App Store traffic; Sandbox for TestFlight and StoreKit testing. You can connect a separate sandbox app on Affiliateo for testing if needed.

Step 1: Generate the .p8 key in App Store Connect

1
Sign in to App Store Connect with an Account Holder or Admin user (only those roles can mint API keys).
2
Go to Users and Access → Integrations → In-App Purchase. (Not the "App Store Connect API" tab next to it, that's a different family of keys.)
3
Click Generate API Key or the + button. Give it a name like "Affiliateo". Apple shows you the Key ID and Issuer ID right after.
4
Click Download API Key. You get a single AuthKey_XXXXXXXXXX.p8 file. Apple only lets you download this once. Save a backup before you leave the page.

Step 2: Connect on Affiliateo

1
In Affiliateo, create or open your mobile_affiliate app. In the wizard, pick Connect stores directly at the provider step, then check Apple App Store (iOS).
2
Paste your Bundle ID, App Apple ID, Issuer ID, and Key ID. Upload the .p8 file. Pick Production or Sandbox.
3
Click Connect Apple. We call Apple's test-notification API with your credentials. If they're valid, you'll see a connected badge and the URL you need to paste back into App Store Connect (next step).

We store the .p8 encrypted at rest with AES-256-GCM. We never log it, never expose it back through the UI, and only use it to sign JWTs for Apple's Server API. Rotate it in App Store Connect → Affiliateo any time.

Step 3: Paste the Server URL into App Store Connect

This is the manual step Apple doesn't let us automate.

1
Back in App Store Connect, go to My Apps → your app → App Information. Scroll to App Store Server Notifications.
2
Paste the URL Affiliateo gave you into the {Environment} Server URL field (Production or Sandbox depending on what you connected). Set the Version selector to V2.
3
Save. Apple should deliver the test notification we triggered in step 2 within ~30 seconds. The connected card in Affiliateo updates its "Last event" timestamp when we receive it.

If you already use the Server URL for your own backend: Apple only allows one URL per environment per app. You have two options: (a) switch the URL to ours entirely (greenfield apps), or (b) keep your URL and have your own server forward the raw { signedPayload } body to our endpoint. Option (b) is a ~20-line proxy.

Step 4: Pass appAccountToken at purchase time

Our SDK auto-mints a stable UUID per (app, affiliate) at identify time and registers it with our backend. Your purchase code reads it from the SDK and hands it to whichever IAP library you use. Apple stamps the UUID onto every transaction in the chain (initial purchase, every renewal, every refund), and our webhook resolves it back to the affiliate.

Pass appAccountToken to StoreKit 2 at purchase time

PurchaseManager.swift
import StoreKit
import Affiliateo

@MainActor
class PurchaseManager: ObservableObject {
    @EnvironmentObject var affiliateo: AffiliateoManager

    func buy(_ product: Product) async throws {
        let token = affiliateo.state.appAccountToken
        let opts: Set<Product.PurchaseOption> = token.map { [.appAccountToken($0)] } ?? []
        let result = try await product.purchase(options: opts)
        // handle result.success / userCancelled / pending
    }
}

Requires iOS 15+. On older iOS versions appAccountToken is nil; the purchase still works but is booked as organic on our side.

How attribution works under the hood

What happens between a user clicking an affiliate link and a renewal showing up on the affiliate's earnings.

1
User taps an affiliate link. We log the click with the device's IP + UA fingerprint.
2
User installs your app (or opens it for the first time). Your SDK calls /api/v1/mobile/identify with their device fingerprint. We match the fingerprint against recent clicks and return a ref_code if matched.
3
SDK mints a UUID v4 keyed by (app, ref_code), persists it locally, and POSTs it to /api/v1/mobile/apple-token to bind it to the visitor row.
4
User makes a purchase. Your code reads appAccountToken from the SDK and passes it to StoreKit. Apple stamps it onto the transaction record.
5
Apple sends us an SUBSCRIBED or ONE_TIME_CHARGE notification. We verify the signature (JWS chain pinned to Apple Root CA G3), decode the inner transaction, look up appAccountToken → visitor → ref_code → affiliate, and record the conversion.
6
On every renewal, Apple sends DID_RENEW carrying the same token. We record it as a renewal conversion under the same affiliate.
7
On refund, Apple sends REFUND (or REVOKE for Family Sharing revocations). We insert a negative-amount conversion, decrement the affiliate's totals, and claw back the commission from their wallet if it was already paid out.

Which Apple events we handle

From App Store Server Notifications V2. Anything not listed gets logged for audit but doesn't change conversion state.

SUBSCRIBEDsubtype INITIAL_BUY or RESUBSCRIBE → records as new subscription
ONE_TIME_CHARGEiOS 17.4+, production from May 27 2025 → records as one-time purchase
DID_RENEWrecords as renewal under the original affiliate
REFUNDrecords refund + decrements affiliate counters + claws back paid commission
REVOKEFamily Sharing revocation, treated the same as REFUND
OFFER_REDEEMED (INITIAL_BUY / RESUBSCRIBE)treated as a new subscription

Known limitations

Things to be aware of before going live.

iOS 15+ required for first-party attribution

appAccountToken only exists in StoreKit 2 (iOS 15+). Older devices can still purchase, but the transaction has no token, so we book it as organic.

Pre-existing purchases stay organic

Customers who connected Apple to an already-running app: only purchases made AFTER you ship the SDK with appAccountToken support are attributable. Historical transactions don't carry the token.

Apple's commission isn't in the payload

Apple charges 15-30% off the top. The price we receive is what the customer paid, not what you net. If you set affiliate commission as a percentage, it's computed on the gross.

Multi-currency aggregate stats

We store each conversion in its native currency (Apple sends ISO 4217 + milliunits). Cross-currency aggregate revenue stats on the affiliate level may mix currencies until our Phase 1.5 FX layer ships.

Sandbox notifications deliver once, no retries

A handler bug in sandbox silently drops the event. Use the Sandbox environment for end-to-end testing; production retries 5 times over 72h.

Troubleshooting

"Credential validation failed" on connect

Apple rejected the JWT we built from your .p8 and Issuer/Key IDs. Double-check the Issuer ID (UUID from App Store Connect → Users and Access → Integrations), the Key ID (10 chars next to your downloaded key), and that the .p8 file matches that exact Key ID. The .p8 is downloadable only once, if you lost it, generate a new key and reconnect.

Connected but "Last event" never updates

Apple isn't delivering notifications to our URL. Most common cause: you didn't paste our URL into App Store Connect, or you pasted it as V1 instead of V2. Go to App Store Connect → App Information → App Store Server Notifications, verify the Server URL matches what Affiliateo shows on the connected card, and that the Version is V2.

Purchases happen but attribution shows organic

Your purchase code isn't passing the SDK's appAccountToken to StoreKit. Check that you're reading it from the SDK after identify completes (use the isLoading flag or await Affiliateo.ready), and that you're running iOS 15+.

"Credentials invalid" banner appears later

Our daily reconciliation cron tried to call Apple's Server API and got 401. Likely the key was rotated or revoked in App Store Connect. Generate a new key, download the new .p8, and reconnect.

Want to switch to RevenueCat (or vice versa)

Disconnect Apple in the edit dialog first. The app goes back to pending_provider and you can connect RevenueCat from there. The two are mutually exclusive: RevenueCat covers iOS already, so stacking them would double-count purchases.

Need help connecting Apple? See the main mobile affiliate guide for the broader overview, or contact support.