All integrations

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

Live

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

POST/v1/app-store/bindBody { signedClickId } (explicit link or user-initiated paste) or { refCode } (typed code). Returns { appAccountToken, offerCode? }.
POST/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.

swift
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.

javascript
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

statusHTTPMeaning
ok200Verified, matched an affiliate, commission accrued (real for a Production app, test for a Sandbox app).
trial200Verified and recognized, but a free trial, so nothing accrues yet. Accrues when it converts to paid.
duplicate200Already processed. Delivery is idempotent, so retries and Apple re-sends are safe.
unattributed200Verified, but no affiliate token on the transaction. Nothing to credit.
ignored200Not a relevant notification type (for example a TEST notification, which carries no transaction).
ignored_non_production200A 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.

HTTPForwarder action
200Accepted (see the status field). Do not retry.
401Bad HMAC: secret mismatch or timestamp skew over 300s. Fix the secret, retry with a fresh timestamp.
400Unverifiable Apple JWS (bad payload). Log it, do not infinite-retry.
5xxTransient 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.

Connect your app

Give this to your AI coding agent

Continue your iOS attribution implementation

Further reading

Compare GoMarketMe alternatives
Get startedI'm an affiliateTalk to us