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
Pick RevenueCat instead if
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
SubscriptionKey_XXXXXXXXXX.p8 file. Apple only lets you download this once. Save a backup before you leave the page.Step 2: Connect on Affiliateo
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.
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.
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
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.
/api/v1/mobile/identify with their device fingerprint. We match the fingerprint against recent clicks and return a ref_code if matched.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.)appAccountToken from the SDK and passes it to StoreKit. Apple stamps it onto the transaction record.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).DID_RENEW carrying the same token. We record it as a renewal conversion under the same affiliate.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 subscriptionONE_TIME_CHARGEiOS 17.4+, production from May 27 2025 → records as one-time purchaseDID_RENEWrecords as renewal under the original affiliateREFUNDrecords refund + decrements affiliate counters + claws back paid commissionREVOKEFamily Sharing revocation, treated the same as REFUNDOFFER_REDEEMED (INITIAL_BUY / RESUBSCRIBE)treated as a new subscriptionFree 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.