Test iOS attribution
Prove the pipeline in sandbox, then confirm one real production sale, safely.
What you are testing
A conversion travels: affiliate link, then a click, then install, then first-launch bind (Attribloom mints the appAccountToken), then the StoreKit purchase carries that token, then Apple sends a signed notification, then your server forwards it, then Attribloom verifies it and accrues the commission. This guide proves that whole chain.
For AI agents
If you are an agent wiring this up: read the iOS integration guide first for the endpoints and the code, then run Path A (sandbox) below. Every step lists the exact request and the exact response to assert on. Do not mark a step done until the observed response matches.
The environment gate is real
An Attribloom app is registered as either Sandbox or Production (appleEnvironment). A Production app rejects Sandbox notifications with 200 ignored_non_production and writes nothing. A Sandbox app accepts them and accrues as a test conversion (flagged, excluded from real payouts). Pick the environment that matches how you are buying.
Before you start
Prerequisites
- Your app is registered in Attribloom and you have its surface id (the last path segment of the Forwarding URL).
- Your server forwards App Store Server Notifications v2 to
/v1/app-store/assn/:surfaceId, signed, withATTRIBLOOM_FORWARDING_SECRETset to the same value as Attribloom. - Your app calls
/v1/app-store/bindon first launch and sets the returned token onproduct.purchase(options:). - You have at least one affiliate link (
https://attribloom.com/r/<code>) or a referral code to enter.
Path A. Sandbox or TestFlight (proves the full pipeline, safely)
Register the app in Attribloom with environment Sandbox. StoreKit sandbox subscriptions renew on an accelerated clock (minutes, not months), so you can watch a renewal and a refund-driven clawback in one sitting. Nothing here touches real money.
- 1
Open an affiliate link
Open
https://attribloom.com/r/<code>so a click is recorded. Confirm the click lands (it returns the App Store interstitial). - 2
Install and launch from TestFlight
On first launch your app should call
/v1/app-store/bind. Assert the response is200with a non-emptyappAccountToken, and that you persisted it to the Keychain. - 3
Make a StoreKit sandbox purchase
Buy the subscription with a Sandbox Apple Account (Settings, Developer, Sandbox Apple Account). Confirm
transaction.appAccountTokenequals the token from bind. - 4
Forward the notification
Apple sends a
SUBSCRIBEDnotification to your server. Your forwarder relays thesignedPayloadto/v1/app-store/assn/:surfaceId. Assert the response is200. - 5
Check the accrued conversion
Because the app is Sandbox, the status is
ok(ortrialfor an introductory free trial) and a test conversion appears on the app's Attribution view. Real payouts exclude it. - 6
Force a refund and watch the clawback
Refund the sandbox transaction (or let it expire). Apple sends
REFUND/EXPIRED; the forwarded notification reverses the commission on the ledger. Assert the balance returns to zero.
Testing against a Production app instead?
If your Attribloom app is registered Production and you send a Sandbox purchase, the assn returns 200 ignored_non_production and nothing accrues. That is the environment gate doing its job, not a bug. Register a Sandbox app to see accruals from sandbox purchases.
Path B. Production (confirms one real sale)
A real Production accrual only exists after a real App Store purchase on a live build. TestFlight and every sandbox mechanism emit environment: Sandbox, so there is no way to produce a Production notification ahead of shipping. Once the instrumented build is live, buy your own subscription through a referred link.
The hold makes a self-test safe
A matured commission is held 7 to 60 days before it is payable (see the commission hold period). A test purchase cannot pay out in that window, so refund it after you confirm attribution and the clawback reverses the accrual. No money leaves.
- 1
Register the app as Production
Set environment to Production and complete verification (Attribloom asks Apple to send a signed test notification with your App Store Server API key and flips the app to Active when it arrives).
- 2
Buy through a referred link
Open your affiliate link, install the live build, and purchase the subscription for real. Confirm the token bound on first launch.
- 3
Confirm the real accrual
The forwarded
SUBSCRIBEDnotification returns statusokand a real conversion appears, attributed to the affiliate, with commission computed on net proceeds (excluding trial, refund, and tax). - 4
Refund to close the loop
Refund the purchase from App Store Connect. The
REFUNDnotification claws the commission back before the hold elapses, so nothing is ever paid out.
Responses to assert on
| 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. |
Troubleshooting
| Symptom | Cause and fix |
|---|---|
assn returns 401 | HMAC mismatch or clock skew over 300s. Confirm ATTRIBLOOM_FORWARDING_SECRET matches byte for byte, timestamp is unix seconds, and the signature is sha256= + hex over "${ts}." + rawBody. |
assn returns 400 | The signedPayload was re-encoded or pretty-printed. Forward Apple's JWS string verbatim. |
status ignored_non_production | You sent a Sandbox purchase to a Production app. Register a Sandbox app, or buy through the App Store for real. |
status unattributed | No appAccountToken on the transaction. The bind did not run, or the token was not set on product.purchase(options:). Check first-launch bind and the purchase call site. |
| No notification arrives | Your app's App Store Server Notifications v2 URL is not set, or your forwarder is not relaying. Use App Store Connect, Request a Test Notification, and confirm your forwarder gets a 200 ignored (not 401/400). |
Start in sandbox to prove the pipeline, then confirm one real production sale. See the iOS integration guide for the endpoints and code.
Further reading