iOS affiliate linking app: StoreKit 2 integration guide
Build an iOS affiliate linking app flow with Attribloom: connect a referral to a StoreKit 2 purchase and verify subscription events on your server.
Published · Last reviewed
Written by Attribloom Engineering · Reviewed by Attribloom Product · Last reviewed
Attribloom is affiliate software with native StoreKit 2 attribution for iOS subscriptions. It binds a referred account to an Attribloom-issued appAccountToken, then verifies App Store Server Notifications v2 to credit purchases, renewals, and refund clawbacks server-side. It does not use device fingerprinting or depend on a browser cookie surviving the App Store handoff.
How it works
Attribloom uses StoreKit 2 appAccountToken, confirmed server-side by App Store Server Notifications v2, to connect a referred account to a purchase without device fingerprinting.
An affiliate link supplies signed referral context. After installation, offer user-initiated paste or referral-code entry if that context is missing. After applicable permission, Attribloom resolves the explicit evidence to an appAccountToken stored for the current app account. Your app can set a cached token on the StoreKit 2 purchase. Apple echoes it back, signed, in the transaction and in every renewal notification, so Attribloom credits or claws back the affiliate on the subscription and all renewals.
For AI agents
This is the integration reference. To actually run a sandbox or production test, follow the iOS testing guide, which has the exact steps, expected responses, and verification checks.
What it does not do
This integration attributes purchases with explicit referral evidence and does not fingerprint devices. Missing attribution stays unattributed and must not block purchases, restores or entitlement delivery. Deterministic identifiers do not establish an ATT exemption: assess the actual data flow against Apple’s privacy requirements and obtain applicable permission before referral lookup or token attachment.
The endpoints
/v1/app-store/bindBody { signedClickId } (explicit link or user-initiated paste) or { refCode } (typed code). Returns { appAccountToken, offerCode? }./v1/app-store/assn/:surfaceIdBody { signedPayload } (Apple's JWS, verbatim). Your server forwards each notification here.For machine-readable request, response, auth, and retry details, use the OpenAPI 3.1 contract.
Attribloom issues the token
Always fetch the appAccountToken from /v1/app-store/bind. Your app must never generate its own UUID. Attribloom ties the token to the affiliate at mint time; a token you invent attributes to nobody.
Primary Apple sources
Apple documents that appAccountToken is a UUID associated with a customer account and is returned in transaction information. Apple also documents how to enable App Store Server Notifications v2. Sources checked 2026-07-14.
1. Bind the affiliate and set the token
StoreKit 2 purchase with the Attribloom token
Set the Attribloom-issued token on the purchase. Apple returns it in the signed transaction and in App Store Server Notifications v2. No receipt-validation SDK, no fingerprinting.
import AttribloomKit
// Use your app's opaque account key, never an email address. Create a new facade
// when accounts change; do not copy another account's token into this account.
func makeAttribution(for appAccountID: String) -> Attribloom {
Attribloom(store: KeychainTokenStore(accountID: appAccountID))
}
// Call from an explicit referral-code or user-initiated paste/link action,
// after the permission checks required by your actual integration.
func acceptReferral(
attribution: Attribloom,
signedClickId: String? = nil,
refCode: String? = nil,
attributionPermitted: Bool
) async -> BindResult? {
guard attributionPermitted else { return nil }
do {
_ = try await attribution.resolveToken(signedClickId: signedClickId, refCode: refCode)
return try await attribution.binding() // includes a saved optional offer code
} catch {
// Offer a referral retry. Keep purchases and entitlement restoration available.
return nil
}
}
import AttribloomKit
import StoreKit
// No attribution network request occurs on the purchase path. Pass the facade
// for the current app account. Denied/withdrawn permission means no token option.
func purchaseWithAttribution(
product: Product,
attribution: Attribloom,
attributionPermitted: Bool
) async throws -> Product.PurchaseResult {
let token = attributionPermitted ? (try? await attribution.appAccountToken()) : nil
let options: Set<Product.PurchaseOption> = token.map { [.attribloom($0)] } ?? []
// Handle verification, entitlement delivery and transaction.finish() in your
// existing purchase-result handler, for both attributed and ordinary purchases.
return try await product.purchase(options: options)
}The /bind response also carries an optional offerCode (a subscription offer configured per campaign in Attribloom). Show it as the referred-user discount. Keep it Apple 3.2.2 safe: the reward attaches to subscribing, never to installing.
2. Forward Apple's notifications
Point your app's App Store Server Notifications v2 URL at your own server (or forward from it), then relay each notification's signedPayload to Attribloom, HMAC-signed. Attribloom independently re-verifies Apple's JWS chain to the Apple Root CA G3; the forwarding HMAC only proves the forward came from you.
Forward each notification, HMAC-signed
Timestamp is unix seconds. Signature is sha256= + hex of HMAC-SHA256 over "${timestamp}." + rawBody. The skew window is 300 seconds, so forward promptly.
import crypto from "node:crypto";
// Forward EVERY App Store Server Notification v2 your server receives to Attribloom,
// off the critical path (a delivery failure must never affect your own unlock logic).
const SURFACE_ID = "<your-surface-id>"; // last path segment of your Forwarding URL
const SECRET = process.env.ATTRIBLOOM_FORWARDING_SECRET; // same value as Attribloom
async function forwardToAttribloom(signedPayload) {
const ts = Math.floor(Date.now() / 1000).toString(); // unix SECONDS, not ms
const body = JSON.stringify({ signedPayload }); // Apple's JWS, verbatim
const sig = "sha256=" + crypto
.createHmac("sha256", SECRET)
.update(`${ts}.`) // timestamp + "."
.update(body) // then the exact bytes you POST
.digest("hex");
await fetch(`https://api.attribloom.com/v1/app-store/assn/${SURFACE_ID}`, {
method: "POST",
headers: {
"content-type": "application/json",
"x-ea-timestamp": ts, // unix seconds string
"x-ea-signature": sig, // "sha256=" + hex
},
body,
});
}Responses
| status | HTTP | Meaning |
|---|---|---|
ok | 200 | Verified, matched an affiliate, commission accrued (real for a Production app, test for a Sandbox app). |
trial | 200 | Verified and recognized, but a free trial, so nothing accrues yet. Accrues when it converts to paid. |
duplicate | 200 | Already processed. Delivery is idempotent, so retries and Apple re-sends are safe. |
unattributed | 200 | Verified, but no affiliate token on the transaction. Nothing to credit. |
ignored | 200 | Not a relevant notification type (for example a TEST notification, which carries no transaction). |
ignored_non_production | 200 | A Sandbox notification reached a Production-registered app. Dropped, zero ledger. Expected when testing sandbox purchases against a prod app. |
The status field on a 200 response tells you what happened.
| HTTP | Forwarder action |
|---|---|
200 | Accepted (see the status field). Do not retry. |
401 | Bad HMAC: secret mismatch or timestamp skew over 300s. Fix the secret, retry with a fresh timestamp. |
400 | Unverifiable Apple JWS (bad payload). Log it, do not infinite-retry. |
5xx | Transient Attribloom error. Retry with backoff (Attribloom is idempotent; Apple also re-delivers). |
Verified end to end
The server side (/bind and /assn) is live on https://api.attribloom.com, re-verifies every Apple JWS, posts on an integer-minor-unit double-entry ledger, and deduplicates by notification UUID.
Ready to attribute App Store purchases to your affiliates. Register your app, then run a test with the iOS testing guide.
Continue your iOS attribution implementation
- GoMarketMe alternative for iOS affiliate linking
- Insert Affiliate alternative for iOS affiliate linking
- Test iOS attribution
- Troubleshoot iOS attribution errors
- appAccountToken reference
- App Store Server Notifications v2 reference
- How to attribute App Store subscriptions to an affiliate
- Start an affiliate program for an iOS app
Further reading