How Apple Search Ads Attribution and AdServices Tokens Really Work
If you're running Apple Search Ads as an indie, attribution is the part that quietly decides whether your numbers mean anything. You can have great keywords and clean bids, but if your install→revenue chain is wired up wrong, your ROAS column is fiction. This post walks through what actually happens between a tap on an ad and a number showing up in your dashboard, and what you as a developer need to do to make that chain trustworthy.
The short version of how attribution happens
Apple Search Ads doesn't use third-party cookies, IDFA, or device fingerprints to attribute installs. Instead, Apple handles attribution itself, on-device, and lets your app ask for the result via a framework called AdServices.
The flow looks roughly like this:
- A user sees your ad on the App Store (Search Results, Search tab, Today tab, or a Product Page).
- They tap it and land on your product page.
- They install and open your app.
- On first launch, your app calls AdServices and receives an attribution token — a short opaque string.
- Your app (or your backend, or a tool like RevenueCat) sends that token to Apple's attribution endpoint.
- Apple responds with a JSON payload describing which campaign, ad group, and keyword drove that install — if any.
That's it. No SDK from Apple beyond AdServices. No device identifier required. No ATT prompt needed for this specific flow, because the token is tied to the install, not to a user identity.
What the AdServices token actually is
The token you get back from AAAttribution.attributionToken() is just an opaque string. It's not the attribution data itself — it's a claim ticket. You exchange it with Apple's server to get the real payload.
When you POST that token to Apple's attribution endpoint, you get back JSON that typically includes fields like:
attribution: true/false — was this install actually driven by an Apple Search Ads tap?orgId,campaignId,adGroupId,keywordId,creativeSetId(when applicable)countryOrRegionclickDateandconversionDateconversionType(e.g. new download vs redownload)adIdfor custom product pages
A few things worth internalizing:
- IDs are numeric, not human-readable. You'll need to map
campaignId→ campaign name yourself, either by exporting from the Apple Search Ads UI or via the Search Ads API. - Apple resolves attribution within roughly 24 hours of the install. If you call too early, you can get
attribution: falseeven when the install was, in fact, ad-driven. The token is valid for retries, so design for that. - Each install has exactly one token. You can call it more than once from the same install (e.g. to retry), but it's tied to that device+install, not the user.
- There is no per-tap revenue. Apple tells you which keyword drove the install. Everything after that — trial start, subscription, IAP, renewal — is your responsibility to stitch together.
The piece Apple doesn't give you: revenue
This is the part that trips up most indies. Apple Search Ads will happily show you spend, taps, installs, CPT, and conversion rate. It will not show you revenue per keyword, because it doesn't know what happens after the install.
To get real ROAS by keyword, you have to join two datasets:
- The attribution payload (campaignId / adGroupId / keywordId from AdServices) — keyed to a specific install.
- Your revenue events (trial, purchase, renewal, refund) — also keyed to that same install or user.
The usual way to do this:
- On first launch, fetch the AdServices token, exchange it with Apple, and store the resulting campaign/ad group/keyword IDs against the user record (or the RevenueCat customer, or your analytics user).
- When that user later converts, your revenue tracker already knows which keyword drove the install.
- Aggregate revenue by keywordId, divide by spend from Apple Search Ads, and now you have keyword-level ROAS.
RevenueCat handles the token exchange and the revenue join for you if you're already using it for subscriptions. If you're rolling your own, you're building the same pipeline by hand — it's not difficult, but the edge cases matter.
Edge cases that quietly wreck your numbers
A few things to watch for, because they show up as "weird numbers" before you realize they're attribution bugs:
- Calling AdServices too early. If you exchange the token within seconds of first launch, Apple may not have finished resolving the attribution. Retry with backoff over the next few hours.
- Not retrying on transient failures. Network errors on the attribution call mean you lose that install's attribution permanently if you don't persist the token and retry.
- Redownloads. A user who deletes and reinstalls can produce a new attribution event.
conversionTypetells you whether it was a new download or a redownload. Decide explicitly how you want to count these for ROAS. - Test devices and TestFlight. AdServices behavior differs in non-production contexts. Don't panic if your dev builds return
attribution: false. - Search Match in its own ad group. If you let Search Match run, the
keywordIdyou get back will map to that ad group's automatic matching, not a keyword you typed in. Filter for these explicitly when analyzing keyword performance. - Currency. Apple reports spend in your account currency. Your revenue tool may report in a different one. Pick one and convert at a consistent rate, or your ROAS will drift.
- Delay between install and revenue. Subscriptions especially. Day-1 ROAS will look terrible compared to day-30 or day-60. Pick a window and compare like-for-like.
A minimal checklist to get this right
If you're starting from scratch or auditing an existing setup, walk this list:
- Link the AdServices framework and call
attributionToken()on first launch. - POST the token to Apple's attribution endpoint with appropriate retry logic.
- Persist
campaignId,adGroupId,keywordId,creativeSetId,adId,clickDate, andconversionTypeon the user record. - Send those IDs to your revenue tracker (RevenueCat attributes, your own analytics, etc.) so revenue events carry them.
- Pull campaign / ad group / keyword names from Apple Search Ads (UI export or API) and join on IDs in your reporting.
- Decide a ROAS window (e.g. day-7, day-30) and stick to it.
- Separate Search Match ad groups from exact-match ad groups in your analysis.
Once this is in place, you can actually answer the only question that matters: which keywords pay for themselves, and which ones quietly lose money.
Closing takeaway
Apple Search Ads attribution is simpler than it looks — one token, one server call, one JSON payload — but the value only appears when you connect it to revenue and analyze it on a window that matches your monetization model. Get the plumbing right first; everything else (bid changes, keyword pruning, country expansion) depends on it.
If staring at the resulting spreadsheet every morning isn't your idea of fun, that's roughly the gap AdsBuddy fills: it reads your Search Ads data and your revenue, ranks the changes worth making today, explains why, and leaves the actual approving and applying to you. Whether you use a tool or not, the underlying discipline is the same — trust your attribution chain before you trust any decision built on top of it.