Attribution ยท how-to

Apple Ads attribution without an MMP: AdServices and RevenueCat

Apple Ads, called Apple Search Ads until April 2025, can tell you which campaign, ad group and keyword produced each install. What the AdServices API returns in 2026, when RevenueCat's built-in view is enough, and what you still can't see until attribution sits next to what users do in the app.

Razvan Popa, runs four live iOS apps ·Updated ·First published · 

Short answer: the AdServices framework gives you deterministic Apple Ads attribution per install (campaign, ad group, keyword, and whether the person tapped the ad or only saw it) with no ATT prompt and no MMP. If you already use RevenueCat, you may not need to build anything: since March 2025 RevenueCat Charts can filter and segment by Apple Ads campaign, ad group, keyword and claim type. What neither gives you is the part after the install: whether the users a keyword brought finished onboarding or came back in week two, the same keyword compared across several apps, and cost per subscriber without a spreadsheet.

This post first ran in July 2026 as a pure AdServices walkthrough. Two things have made that framing incomplete. Apple renamed Apple Search Ads to Apple Ads on April 14, 2025 (9to5Mac), and it keeps adding fields to the attribution payload. And RevenueCat shipped keyword and claim-type segmentation in Charts on March 27, 2025 (changelog), which answers "which keyword brings revenue" for many indie apps without any code of your own. So this version starts with what changed, then what RevenueCat covers, then the pipeline for everything it doesn't.

What changed since 2025

Most of these come from Apple's own AdServices changelog.

WhenChangeWhat it means for you
Nov 2024claimType field added, value ClickEach attributed payload says how the claim was made.
Mar 27, 2025View-through attribution: claimType: Impression and an impressionDate fieldSome attributed installs never tapped your ad. Count them apart from taps.
Mar 27, 2025RevenueCat Charts filter and segment by Apple Ads keyword and claim typeKeyword revenue with no code, if you're on RevenueCat.
Apr 14, 2025Apple Search Ads renamed Apple AdsSame framework, same endpoint.
Oct 2025Pre-order attribution, conversionType: PreOrderOnly relevant if you run pre-order campaigns.
Jan 2026supplyPlacement fieldYou can tell search results, Search tab, Today tab and product page ads apart per install.
Sep 2026Campaigns using age or gender targeting return attribution: falseThose installs look unattributed. Drop the targeting if you need the data.
Jan 26, 2027 (announced)Apple will sunset the Campaign Management API; the Apple Ads Platform API replaces it (Apple Ads API docs)Scripts that map IDs to names through the old API need moving before then.

When RevenueCat's Apple Ads view is enough

If you use RevenueCat and your question is "which keywords bring revenue", start there. Turn on token collection in the SDK:

Purchases.shared.attribution
    .enableAdServicesAttributionTokenCollection()

RevenueCat then requests the attribution record from Apple and stores campaign, ad group, keyword and claim type on the customer (RevenueCat's Apple Ads docs). Charts can then be filtered and segmented by those fields, including click-through versus view-through. The docs say to allow up to seven days for attribution data to arrive, and names instead of numeric IDs need the Apple Ads Advanced connection through Sign in with Apple.

For one app and a revenue question, that's a good answer and it costs nothing extra. Three questions stay open:

  • What those users did in the app. RevenueCat sees purchases. It doesn't see whether a keyword's users finished onboarding, which features they used, or whether they came back in week two. For the first weeks after install, before trials convert, behavior is the only early signal you have.
  • What it cost. RevenueCat's Apple Ads documentation describes no spend import, so cost per subscriber still means pulling spend from Apple Ads and dividing by hand.
  • More than one app. Charts are scoped to a RevenueCat project. If each of your apps is its own project, comparing a keyword's results across apps means switching projects and copying numbers.

Those need the raw attribution on your own user record, next to events and purchases. The rest of this post is that pipeline.

Step 1: get the token

Import AdServices (iOS 14.3 and later) and call AAAttribution.attributionToken(). Three properties matter:

  • The token lives for 24 hours (Apple's reference). You don't have to resolve attribution in the first session. Keep the token and finish the job on a later launch.
  • Expect it to throw in the simulator. Catch the error and test the real path on a device.
  • The call is synchronous. Generate the token off the main thread so the user's first launch doesn't stutter.

Do this once per install and record that you did. Attribution is a fact about the install, not the session, so resolving it again every launch is wasted work and a source of conflicting data.

Step 2: POST it to Apple

Send the raw token as the body of a POST, as plain text:

POST https://api-adservices.apple.com/api/v1/
Content-Type: text/plain

<the token string>

Apple documents four responses:

  • 200: a JSON payload. attribution: true with the IDs described below, or attribution: false when Apple finds no matching ad interaction.
  • 404: the record isn't ready yet, or the token is past its 24 hours. Apple's guidance is to retry at 5-second intervals, at most three times. Since May 2024 Apple holds the connection for up to a second before answering 404, so don't set a client timeout below one second.
  • 400: the token is invalid. Generate a new one instead of retrying.
  • 500: Apple's server is down. Retry later.

You can make the call from the device or send the token to your backend and resolve it there. The device is simpler and fine for most indie apps. A backend call survives the app being killed mid-retry and lets you change the logic without an App Review round. Either way, if all three attempts return 404, keep the token and try once more on the next launch inside the 24 hours.

Step 3: read the payload

Field names and notes below are from Apple's attribution payload reference.

FieldWhat it isWatch out for
attributiontrue or falsefalse means "not Apple Ads", not "organic"
orgIdYour Apple Ads account
campaignIdThe campaign
adGroupIdThe ad group
keywordIdThe keywordNot returned when search match is on
adIdThe adDevices on iOS 15.2 and later
claimTypeClick or ImpressionA tap within 30 days beats a view within 24 hours
clickDate / impressionDateWhen the tap or view happenedOnly for users who allowed tracking
conversionTypeDownload, Redownload or PreOrderRedownloads are returning users
countryOrRegionThe campaign's country or region
supplyPlacementWhere the ad ranAdded January 2026

On consent: Apple's current reference no longer spells out the split, but RevenueCat and Adjust document it the same way. Users who didn't allow tracking get a "standard" payload without the tap or view timestamp; users who did get a "detailed" one with it. Everything you need for keyword decisions arrives in both.

Read attribution: false precisely. It means Apple found no Apple Ads interaction for this install. The user may have come from a Meta campaign, a Reddit thread or a friend. Store it as "not Apple Ads" rather than "organic", and rather than leaving a null you'll misread in three months.

Two fields change how you count. conversionType: Redownload is someone coming back, so if you compute cost per new subscriber, count redownloads separately or your best "keyword" may turn out to be your own churned users returning. And claimType: Impression marks installs where the person saw the ad in the last 24 hours but never tapped it. Keep view-through installs in their own column until you've seen whether those users behave like tap-through users.

AdServices is the deterministic half of iOS attribution. The aggregate half (AdAttributionKit and SKAdNetwork, for every other network) is in do you need an MMP as an indie iOS developer.

Step 4: turn IDs into names

The payload carries numeric IDs only. Three ways to name them:

  1. The Apple Ads API. Apple's reference points each ID at a report (campaigns, ad groups, keywords). The Apple Ads Platform API supersedes the Campaign Management API, which Apple says will be sunset on January 26, 2027, so write new code against the Platform API.
  2. A lookup table you keep by hand. With a handful of campaigns and a few dozen keywords, a text file you update when you edit campaigns covers it. The IDs don't change once created.
  3. RevenueCat, if you connected Apple Ads Advanced, shows names in Charts.

Store the raw IDs on the user record and resolve names when you display them. Renaming a campaign shouldn't rewrite history.

Step 5: join it to behavior and revenue

Campaign and keyword IDs on a user record become useful next to what that user did and paid. That needs behavior and purchases on the same record, which is the one-ID pattern: one user ID shared by your analytics and RevenueCat, with the attribution fields stored on it. With that join in place you can ask what RevenueCat Charts can't answer:

  • Onboarding completion and week-two return per keyword, readable days before any trial converts.
  • Trial rate and revenue per keyword, split by tap-through and view-through.
  • The same keyword across several apps, side by side.

The number worth building toward is cost per subscriber, not cost per install. Spend per campaign and keyword comes from Apple Ads reports; your joined data supplies subscribers per the same IDs. Divide monthly, not weekly: on a few hundred euros of spend, weekly cost per subscriber swings on two or three people, and a keyword needs a few dozen attributed installs before its conversion rate says much. A keyword with expensive taps can still be your cheapest per subscriber if its users convert better, and the Apple Ads console alone can't show you that.

Gotchas

  • One resolution per install, persisted. Not per launch.
  • Search match hides the keyword. Apple doesn't return keywordId when search match is enabled. Keep search-match discovery in its own ad group so your other installs keep their keyword IDs.
  • Age and gender targeting now kills attribution. Since September 2026 those campaigns return attribution: false.
  • Paused campaigns keep attributing. A tap counts for 30 days and a view for 24 hours, so installs keep arriving after you pause. Don't unpause in a panic.
  • Developer mode returns test data. Apple's reference shows a sample payload with placeholder IDs like 1234567890; if you see those in production data, check that the device isn't in developer mode.
  • TestFlight and simulator installs don't attribute. Fail quietly and flag those devices as internal. At indie volume, a handful of test devices can bend every number.
  • Log failures distinctly. "Not Apple Ads", "token failed" and "gave up after retries" are three different facts. One bucket for all three hides integration bugs.
Where FolioKit fits: the FolioKit SDK resolves Apple Ads attribution through AdServices, with no ATT prompt, and stores it under the same user ID it hands to RevenueCat, so a user's campaign and keyword sit next to their events and revenue. Growth shows installs by source. There is no per-keyword report in the dashboard today. Details in the SDK docs.

FAQ

Is Apple Ads the same as Apple Search Ads?

Yes. Apple renamed it on April 14, 2025. The AdServices framework, the endpoint and the payload fields carried over; Apple's developer documentation now uses the new name.

Does Apple Ads attribution need the ATT prompt?

No. Campaign, ad group, keyword, claim type, conversion type and country arrive without tracking consent. Only the tap or view timestamp is reserved for users who allowed tracking.

Is RevenueCat's Apple Ads integration enough?

For keyword-level revenue in one app, usually yes. It doesn't show what those users did inside the app, its Apple Ads documentation describes no spend import, and each RevenueCat project is charted on its own.

Why do I get 404s for a valid token?

The record isn't ready yet, or the token is older than 24 hours. Retry at 5-second intervals up to three times as Apple recommends, then once more on a later launch inside the 24 hours.

Why is keywordId missing?

Search match is on for that ad group. Apple doesn't return keyword IDs for search-match installs.

Can AdServices attribute Meta or TikTok installs?

No. AdServices covers Apple Ads only. Other networks measure through AdAttributionKit or SKAdNetwork, which send aggregate postbacks with no user attached.

Sources

FolioKit is analytics for indie iOS developers: Apple Ads attribution from AdServices, stored on the same user record as in-app behavior and RevenueCat revenue, for every app you run.

Start free