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

Four values and one file. The Bundle ID and App Apple ID usually fill themselves in from your App Store link.

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

The 10-character ID of the In-App Purchase key. It fills itself in from the file name when you drop the key.

In-App Purchase key (.p8 file)

Generated in the next step. Its file is named SubscriptionKey_XXXXXXXXXX.p8. Apple lets you download it only once, so keep a copy somewhere safe.

App Store Connect API key (optional)

A second key that lets us import your products and prices, and check your App Apple ID before release. It needs the App Manager role or higher. Its file is named AuthKey_XXXXXXXXXX.p8.

Environment

Production for App Store traffic; Sandbox for TestFlight and StoreKit testing, where a test purchase only shows that notifications arrive ("Receiving notifications") and is never recorded as a sale. 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 SubscriptionKey_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 Affiliate app and switch on the Mobile app rail. Pick Apple / Google IAP as the provider, then tick Apple App Store (iOS). On an app's edit screen, add Apple App Store with the + button instead.
2
Drop the .p8 file into the box, click the box to choose it, or paste the key. The Key ID fills itself in from the file name; type the Issuer ID. The Bundle ID and App Apple ID fill in from your App Store link, so check them. Already connected another app with the same key? Pick Use a saved key instead of uploading it again.
3
Click Connect Apple. We check the key without changing anything at Apple: that the file is a valid key, that it can check your purchases, and that the App Apple ID belongs to this Bundle ID. A check that fails says what to fix. When they pass, the card shows Valid credentials and the Server URL for the next step.

We store the .p8 encrypted at rest with AES-256-GCM. We never log it and never send it back to your browser (the card shows only the last four characters of each ID). We only use it to sign requests to Apple's Server API. To rotate it, create a new key in App Store Connect and connect again.

Step 3: Paste the Server URL into App Store Connect

Recommended, so sales show up within seconds. Without it, we read Apple's notification history every hour with your In-App Purchase key, so sales still arrive within about an hour. Apple doesn't let us automate this step.

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. Back in Affiliateo, click Send test notification on the connected card. "Apple reached Affiliateo" means you are done. "Apple sent it to a different URL" means App Store Connect still has another URL saved, so paste ours again. Any other message is Apple's reason for not delivering it. The card then shows when the last notification arrived.

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. If you do neither, we still read Apple's notification history every hour, so sales arrive within about an hour.

Already using RevenueCat for this iOS app? Do this instead of the steps above, because RevenueCat needs Apple's Server URL. In RevenueCat, open your iOS app's settings and paste the URL Affiliateo gave you into Apple Server Notification Forwarding URL, then save. RevenueCat passes every notification on to us unchanged, so both keep working.

Optional: add an App Store Connect API key

The In-App Purchase key cannot read your product list. A second key can, so we import your products and prices for you.

1
In App Store Connect, go to Users and Access > Integrations > App Store Connect API > Team Keys.
2
Click + to generate a key with the App Manager role or higher, then download it. Its file is named AuthKey_XXXXXXXXXX.p8.
3
On the connected Apple card in Affiliateo, open App Store Connect API key, drop the file (the Key ID fills itself in), and click Save key. You can also add it while connecting.

Step 4: Pass appAccountToken at purchase time

The SDK gives every device its Affiliateo visitor id as appAccountToken, whether or not the person came from an affiliate link. Your purchase code reads it from the SDK and hands it to whichever IAP library you use. Apple stamps it onto every transaction in the chain (initial purchase, every renewal, every refund), and our webhook resolves it back to the phone, and to the affiliate when the person came from one. (Older SDK versions minted a separate UUID only for people from an affiliate link; purchases made under those still resolve.)

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.

Tagging purchases with your own account id

If your own server reads appAccountToken to know whose purchase it is, you can pass your own account id there instead of the SDK's token. Then call identify() with that exact same id right after sign-in, before the person can buy: that is how we find the phone. Apple only accepts a UUID here, so this works when your account ids are UUIDs.

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
The SDK hands your app this device's visitor id as its appAccountToken, for every user, so a sale is tied to the phone even when nobody referred it. (Older SDK versions minted a separate UUID only for matched users and registered it with us; purchases made under those still resolve.)
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, tie the sale to the phone, and record the conversion (credited to the affiliate when the person came from one).
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, whether Apple sends them to us or we read them from Apple's notification history every hour. 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

Free access for affiliates

You can give an affiliate your paid app for free, for a length you pick. Apple and Google can't give anyone a subscription from outside your app, so your app asks us: pass the email to identify() when someone signs in, call freeAccess() every time the app opens, and unlock while it says active, next to your own purchase check. How free access works

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

Apple sends each sandbox notification once, with no retries. Sandbox mode shows that notifications reach Affiliateo (the card reads "Receiving notifications"); test purchases are never recorded as sales and earn no commission. Production retries 5 times over 72h.

Troubleshooting

A check fails when you connect

Each failed check says what to fix. "Not a valid App Store key": drop the In-App Purchase key file itself (SubscriptionKey_...p8), not an App Store Connect API key or a certificate. "Apple refused this key": the Issuer ID or Key ID does not match the file, or the key was revoked. The Key ID is in the file name, and the Issuer ID is shown above the key list. "This App Apple ID belongs to another app": copy both IDs again from App Information.

The test notification goes to a different URL, or nothing arrives

App Store Connect still has another URL saved, or the version is set to V1. Go to App Information, App Store Server Notifications, paste the Server URL from the connected card, set Version 2, save, and send the test again. If RevenueCat handles this app, paste our URL into RevenueCat's forwarding field instead (step 3).

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+. Passing your own account id instead? Check that identify() ran with that exact same id on the phone before the purchase.

"Needs attention" appears later

Our hourly check with Apple got a refusal, usually because the key was revoked in App Store Connect. Open the badge to see which check failed. Fix it and press Check again, or disconnect and connect a new key.

Want to switch to RevenueCat (or vice versa)

Nothing has to be disconnected first. On the app's edit screen, add the new one with the + button and keep both connected while your app update rolls out. A purchase both report is counted once, and renewals stay with the affiliate who earned them. Once most customers have the new version, remove the old one with its X. While RevenueCat still handles this iOS app, keep Apple's Server URL on RevenueCat and use its forwarding URL (step 3).

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