# iOS attribution paths

What each iOS path reports, its limits and the practitioner rules for using it. Facts checked against Apple's live documentation on 2026-10-17. Re-check version boundaries before quoting them.

## Contents

- ATT and the IDFA
- ATT in the EU from iOS 27.2
- Privacy manifests and tracking domains
- SKAdNetwork 4
- Crowd anonymity rules
- Conversion values are set on the device
- Conversion value minimum rules
- AdAttributionKit
- Attribution rules and cooldowns
- How the two frameworks interoperate
- Postback copies to your own endpoint
- Verifying and counting postbacks
- Testing with Developer Mode
- Apple Ads: three views of one channel
- Google on-device conversion measurement
- Practitioner rules

## ATT and the IDFA

- The app adds `NSUserTrackingUsageDescription` and calls `requestTrackingAuthorization(completionHandler:)`. The system does not show the prompt unless the app state is active. A call made during launch before the app is active shows nothing.
- In some cases the system shows no prompt and runs the completion handler at once: tracking is restricted on the device, the person has turned off "Allow Apps to Request to Track", a request is already pending, or the app is not active. A request is therefore not proof that a prompt was shown.
- The prompt is one-shot. Apple's pages describe requesting while the status is `notDetermined`; once a person answers, a later request does not show the prompt again, on any OS version and in any region. The only exception is the EU re-prompt a year later from iOS 27.2 (next section). The person can still change the choice in Settings.
- If the system dismisses the prompt without a decision from the person (for example, the app leaves the active state), the status stays `notDetermined` and the app must call the method again.
- The IDFA returns `00000000-0000-0000-0000-000000000000` on iOS 14.5 and later when the app has not requested authorization, when the person declines, when a profile restricts it, and always in Simulator.
- Apple's definition of tracking covers linking app data with other companies' data for targeted advertising or advertising measurement, and sharing with data brokers. Apple's FAQ says a hashed email or phone number cannot replace ATT permission for tracking.
- Fingerprinting is banned: deriving data from device, browser, location or network properties to identify a device. Apps that include SDKs doing it can be rejected.
- The IDFV can be used for analytics across apps from the same developer without ATT, but not combined to track across other companies' apps and sites.
- App Review Guideline 5.1.2(i): tracking needs explicit permission through the ATT APIs, and an app may not require tracking to be enabled to access functionality or content, use the app, or receive money or other compensation.
- Apple's ad attribution FAQ says AdAttributionKit does not need the ATT prompt. Any other tracking in the same app still does.

## ATT in the EU from iOS 27.2

Apple's user privacy page announces these changes for the EU beginning with iOS and iPadOS 27.2. The rules for when an app must ask do not change. Check the privacy page and each API page for the current boundary before quoting it.

- New API: `requestTrackingAuthorization(preferExpandedInterface:additionalInformationAction:completionHandler:)`, available from iOS 27.2. It can ask for a full-page sheet instead of the alert, and add an "Additional Information" button that runs the app's own action.
- New key: `NSUserTrackingMarkdownUsageDescription`, available from iOS 27.2. Markdown text for the full-page sheet. Outside the EU the system uses `NSUserTrackingUsageDescription`.
- France, Germany, Italy, Poland and Romania: only the alternative prompt is available. The system shows the full-page sheet whichever request method the app calls, with the Markdown text when the key is present.
- Elsewhere in the EU: the Markdown key page says the system uses the Markdown text only when the app calls the new API with `preferExpandedInterface` true.
- A tap on Additional Information dismisses the sheet without an answer. The completion handler receives `notDetermined`; the app shows its own information screen and then calls the method again. Apple says not to send the person back to the prompt if they opted out of every relevant consent choice on that screen.
- Re-prompt after a year: in the EU the system records the date of an answer and allows another request a year later, whether the answer was allow or deny, unless the person turned off tracking requests in Settings.

## Privacy manifests and tracking domains

- An SDK or app that tracks sets `NSPrivacyTracking` to true and lists its tracking hosts in `NSPrivacyTrackingDomains`.
- If the person has not granted tracking permission, network requests to those domains fail and the app receives an error.
- Rule: read the privacy manifest of every measurement SDK. If an MMP or network lists the host that also carries non-tracking measurement, expect that traffic to fail for every non-authorized user and ask the vendor which host serves non-tracking traffic.

## SKAdNetwork 4

- Three conversion windows: days 0 to 2, 3 to 7 and 8 to 35. Apple states the first window ends two days after the user first launches the app, the second seven days after, the third after 35 days.
- Random delay after a window ends: 24 to 48 hours for the first postback, 24 to 144 hours for the second and third.
- Ads signed with SKAdNetwork 3 or earlier get one winning postback only. Non-winning attributions also get only the first postback.
- Postback data tiers run from Tier 0 to Tier 3, based on crowd size for the publisher, the advertised app, the install country and the source identifier:
  - First postback: Tier 0 gives a 2-digit `source-identifier` and no conversion value. Tier 1 gives 2 digits and a `coarse-conversion-value` (`low`, `medium`, `high`). Tier 2 gives 2 to 4 digits and a fine `conversion-value` (0 to 63). Tier 3 adds `country-code` and the source app or domain.
  - Second and third postbacks at Tiers 1 to 3: a 2-digit source identifier and a coarse value. Tier 0 gets no second or third postback.
- `updatePostbackConversionValue(_:coarseValue:lockWindow:completionHandler:)` with `lockWindow` true finalises the current window early. Later updates in that window are ignored and the random delay still applies.
- To register attribution, the advertised app must call one of the conversion value update methods on first launch.
- The postback signature does not cover `conversion-value` or `coarse-conversion-value`. A valid signature proves the attribution, not the value.
- `redownload` true means a device with the customer's Apple Account installed the app before.
- SKAdNetwork for Web Ads attributes web ad clicks in Safari on iOS 16.1 and later that lead to App Store installs. It does not cover clicks in `SFSafariViewController` or `WKWebView`.
- Apple requires apps that display ads to list each network's ID in `SKAdNetworkItems` (lowercase, case-sensitive). That list is a publisher-side setting; the advertised app's measurement depends on its own update calls and the networks' registrations.

## Crowd anonymity rules

The tier decides what each postback can answer. Creative or ad set reads below the 2-digit level are possible only on first postbacks at Tier 2 or 3. Decisions on days 3 to 35 (later revenue, retention) are possible only at the 2-digit level, at every tier.

1. Ask each network how it maps the 2, 3 and 4 source-identifier digits to campaign, ad set and creative. Record the mapping in the measurement map. Ask whether the first two digits carry the dimension your later-window decisions need.
2. Report per network per week: the share of winning first postbacks with only 2 digits, and the share with no conversion value.
3. Default threshold, to adjust and record in `decisions.md`: if more than 30% of a network's winning first postbacks have no value or only 2 digits, consolidate that network's campaigns, ad sets or geos before reading ad set or creative results from postbacks. Crowd size drives the tier, so fewer, larger groupings raise it.
4. Never infer creative performance from later-window postbacks.

## Conversion values are set on the device

- Apple's update methods for SKAdNetwork and AdAttributionKit conversion values are calls the app makes on the device, while it runs and the conversion window is open. Apple documents no server-side way to set a value.
- A server-to-server event cannot change a conversion value unless the MMP documents a server-side conversion value feature. Ask the MMP whether S2S events can drive conversion values, and record the answer with the date in `decisions.md`.
- When purchases or trials are verified on the backend, the app must learn the verified result while the window is open. It then either calls the update itself, or logs a revenue-free SDK event that the MMP's conversion value schema maps (for example `trial_verified` with no revenue field). Revenue keeps one sender, the backend.
- Failure mode: purchases move to S2S and the conversion value signal disappears. Detect it by comparing, before and after the release, the share of winning first postbacks whose value is at or above the purchase or trial value. A drop to near zero with steady S2S purchase counts is this defect.

## Conversion value minimum rules

Use these when no dedicated conversion value design skill is installed:

- Build the schema around one early decision (for example, trial started within 48 hours), not a revenue ladder.
- Few states: a handful of fine values for the first window, each mapped to a coarse value.
- The decision event maps to a coarse value no other state uses, so it still reads at Tier 1 and in later windows.
- Fine values exist only in the first postback at Tier 2 or 3. Design the second and third windows on coarse values alone.
- Precedence: when several events fire in a window, the highest-ranked state sets the value. A lower-ranked event never overwrites a higher value already set.
- Retries and repeated events never raise a value twice. A value records a state reached, not a count of calls.
- A missing value is unknown, not zero. Report it as its own bucket.
- Lock a window only after the deciding event has fired, never on a timer.
- Version the mapping and record who updates it (see the release record rules).

Fictional first-window mapping for a subscription app. It is a planning example, not an Apple default:

- Fine 0: first open recorded, coarse `low`.
- Fine 10: account created, coarse `low`.
- Fine 20: first useful task completed, coarse `medium`.
- Fine 40: first verified trial start or purchase, coarse `high`.

Planning record kept beside the reporting model. It is not SDK configuration:

```yaml
schema_version: subscription_app_v1
decision: compare_early_value_by_acquisition_campaign
window_scope: first_conversion_window
activation_event: first_useful_task_completed
purchase_event: purchase_verified
purchase_authority: subscription_backend
device_signal_for_verified_purchase: revenue_free_sdk_event
unknown_value_policy: retain_as_unknown
owner: app_measurement_lead
```

## AdAttributionKit

- Available from iOS and iPadOS 17.4 for install attribution, in the App Store and alternative marketplaces.
- Re-engagement from iOS 18, click-through only:
  - The ad network must sign the impression with `eligible-for-re-engagement` true in the JWS payload. Without it there is no re-engagement conversion.
  - A re-engagement URL is optional. Passed through `handleTap(reengagementURL:)`, it deep links into the installed app. It must be a registered universal link of the advertised app, otherwise the system ignores it and opens the app as usual. The system appends `AdAttributionKitReengagementOpen` to it.
  - Re-engagement is limited per app per month and per device per year. By default the system creates a re-engagement conversion for a second ad tap after an install (see cooldowns).
- Default eligibility: click-through installs within 30 days of the tap, view-through within 24 hours of the view.
- The app must call a conversion value update method at least once to begin the conversion window; Apple recommends doing it at first launch. After install the device has 60 days to send the first update; after a re-engagement, 2 days.
- Install conversions produce up to five non-winning postbacks (`did-win` false) for other qualifying networks.
- Apple's June 2025 update added multiple conversion windows, `country-code`, attribution rules and cooldowns. Apple's pages for windows, cooldowns and country code state no minimum OS. AppsFlyer's coverage of that update ties it to iOS 18.4, and Apple puts conversion tags (`PostbackUpdate.conversionTag`) at iOS 18.4. Treat 18.4 as the boundary for these features and confirm on Apple's API pages at run time.
- Conversion tags, iOS 18.4: on a re-engagement open the system passes a conversion tag in the URL under the `AdAttributionKitReengagementOpen` key. Persist it with your own reference and pass it in `PostbackUpdate` to update only that conversion when windows overlap. Without a tag the update goes to the most recent conversion.
- Update API: `updateConversionValue(_:coarseConversionValue:lockPostback:)` and, from iOS 18, `PostbackUpdate` with `conversionTypes` to target install or re-engagement postbacks. Without conversion types, an update applies to all active postbacks.
- Postback fields: `postback-identifier`, `did-win`, `postback-sequence-index` (0, 1, 2 for the window), `conversion-type` (`download`, `redownload`, `re-engagement`), `source-identifier`, `ad-interaction-type` (`view`, `click`), optional `conversion-value`, `coarse-conversion-value`, `country-code`, `publisher-item-identifier` and `marketplace-identifier`. No field identifies a person or device.

## Attribution rules and cooldowns

Attribution windows and cooldowns live in `Info.plist`. The full path, as documented on the checked date:

```text
AdAttributionKitConfigurations (dictionary)
  AttributionWindows (dictionary)
    global, or a key named with the ad network ID (dictionary)
      install (dictionary)
        click: 1 to 30 days (default 30, so only shorter)
        view: 1 to 7 days (default 1, so up to 7 times longer)
        ignoreInteractionType: view or click (ad network entries only)
  AttributionCooldown (dictionary)
    install-cooldown-hours: 0 to 720
    re-engagement-cooldown-hours: 0 to 720
```

Illustrative entry with a fictional network ID:

```xml
<key>AdAttributionKitConfigurations</key>
<dict>
  <key>AttributionWindows</key>
  <dict>
    <key>global</key>
    <dict>
      <key>install</key>
      <dict>
        <key>view</key>
        <integer>3</integer>
      </dict>
    </dict>
    <key>example.adattributionkit</key>
    <dict>
      <key>install</key>
      <dict>
        <key>click</key>
        <integer>7</integer>
        <key>ignoreInteractionType</key>
        <string>view</string>
      </dict>
    </dict>
  </dict>
  <key>AttributionCooldown</key>
  <dict>
    <key>install-cooldown-hours</key>
    <integer>24</integer>
  </dict>
</dict>
```

- Precedence: ad network, then global, then system default. `ignoreInteractionType` in a global entry is not recognised.
- Rule: a window or cooldown change moves attributed counts against every network's own report. Record each change with its release and date, and check it first when a gap opens.
- A configured window or cooldown changes attribution only for users on an OS version that supports it (18.4, as above). Report that OS share beside any comparison that depends on the setting.

## How the two frameworks interoperate

- The system sorts SKAdNetwork and AdAttributionKit impressions together and picks one winner per conversion. Click-through beats view-through; within each group the most recent wins; at most six impressions are considered.
- If the app integrates both frameworks, it calls both update APIs. SKAdNetwork mirrors its update calls into AdAttributionKit.
- `.skadnetwork` and `.adattributionkit` network IDs work in both frameworks.
- Rule: one component owns conversion value updates (the MMP SDK or the app's own code, never both), and reports never add SKAdNetwork and AdAttributionKit installs.

## Postback copies to your own endpoint

- SKAdNetwork: `NSAdvertisingAttributionReportEndpoint`, copies of winning postbacks sent to `https://<domain>/.well-known/skadnetwork/report-attribution/`.
- AdAttributionKit: the Xcode key shown as "AdAttributionKit - Postback Copy URL" (`AttributionCopyEndpoint` in Apple's prose), sent to `https://<domain>/.well-known/appattribution/report-attribution/`. Re-engagement copies need the separate Boolean "AdAttributionKit - Opt in for Reengagement Postback Copies". Apple's page names the key two ways, so confirm it in Xcode.
- The system uses the registrable domain and ignores subdomains; the server needs a valid certificate.
- Each key holds one URL, so one party receives direct copies. With an MMP, use the MMP's documented endpoint for both keys. During an MMP migration, decide which vendor owns the keys and for how long.
- Copies do not replace the network's own postback integration with the MMP. Confirm in the MMP's postback copy documentation what it still needs from each network.
- Evidence that copies work: a received SKAdNetwork copy and a received AdAttributionKit copy from a released app version, with the version and date. Do not accept a TestFlight build as that evidence; MMP copy guides tie the check to a published version, so confirm the requirement in your MMP's guide. An empty report can also mean no eligible traffic, so check traffic and release adoption before calling the key broken.

## Verifying and counting postbacks

- AdAttributionKit: verify the JWS with the key named by `kid`. Production uses `apple-cas-identifier/0`; development uses `apple-development-identifier/0` (end-to-end) and `apple-development-identifier/1` (developer settings). Ignore production postbacks that fail validation. Deduplicate on `postback-identifier`.
- SKAdNetwork: read the postback version first. A postback without a `version` field is version 1. Verify `attribution-signature` over the version-specific field order with that version's public key, and deduplicate on `transaction-id`.
- Answer with HTTP 200. AdAttributionKit retries after a 500; SKAdNetwork retries after any response other than 200. Both retry up to nine times over up to nine days, so the endpoint must be idempotent.
- Count installs only from postbacks that are verified, unique, winning, production, first window and a first-time download.
- AdAttributionKit: `did-win` true, `postback-sequence-index` 0 and `conversion-type` `download`.
- SKAdNetwork, read by version. Apple's parameter page lists the first version of each field:
  - `did-win` exists from version 3. Below 3 only winners receive postbacks, so treat a missing `did-win` as winning.
  - `postback-sequence-index` exists from version 4. Below 4 there is one postback per install, so treat a missing index as the first and only postback.
  - `redownload` exists from version 2. For version 1, store it as unknown, never as false. Count those installs and report the unknown share beside the total.
  - The source field is `campaign-id` in versions 1 to 3 and `source-identifier` from version 4. Store both in one source column with the version beside it.
- A pipeline that requires `did-win`, `postback-sequence-index` and `redownload` on every postback drops every pre-version-4 install, including Apple Ads while it signs with SKAdNetwork 1 to 3.
- Report redownloads on their own line (AdAttributionKit `redownload`, SKAdNetwork `redownload` true). Whether a decision counts them is written in the decision map, never mixed in silently.
- Count re-engagement postbacks in a separate re-engagement metric and never add them to installs. Apple's own sample re-engagement postback carries `postback-sequence-index` 0 and `did-win` true, so the window and winner filters alone do not exclude it. Apple's pages spell the value both `re-engagement` and `reengagement`; treat any value other than `download` as not a new install.
- Later-window postbacks are more observations of the same conversion. Non-winning postbacks are not installs.

## Testing with Developer Mode

- AdAttributionKit Developer Mode needs iOS 18 or later and a production Apple ID (sandbox IDs are not supported). It turns itself off after two weeks.
- Development windows shrink to 3 minutes each (0 to 3, 3 to 6, 6 to 9 minutes) and delays to 5 to 10 minutes. Development postbacks get the highest tier and are signed with development keys.
- Production impressions beat development impressions. A passing test proves configuration, not production coverage or timing.

## Apple Ads: three views of one channel

- Apple's Apple Ads attribution overview says Apple Ads registered on 10 April 2025, beginning with SKAdNetwork versions 1 to 3. It does not mention iOS 26.2. While Apple Ads signs with SKAdNetwork 3 or earlier, each install gets one winning postback and no second or third window.
- AppsFlyer's Apple Ads bulletin adds that these SKAdNetwork postbacks are click-through only, with no view-through, and that the campaign ID carries only the placement (search results, search tab, today tab, product pages). A cross-network read on postbacks compares Apple Ads at placement level and on clicks only.
- iOS 26.2 and later, as stated by MMPs (Apple's Apple Ads page, checked 2026-10-17, does not cover it; verify live):
  - AppsFlyer: from iOS 26.2, Apple Ads postbacks are signed and sent using AdAttributionKit instead of SKAdNetwork; iOS 26.1 and earlier keep SKAdNetwork. Both postback copy keys are needed.
  - Branch: without adding AdAttributionKit to `Info.plist` alongside SKAdNetwork, Apple Ads attribution will be interrupted for users on 26.2 and later, and for those users SKAdNetwork postbacks will mirror AdAttributionKit postbacks.
  - No Apple page states whether attribution continues for an app without the AdAttributionKit configuration. Do not tell users that it does.
- Required action, the same wherever this skill states it: configure AdAttributionKit alongside SKAdNetwork, set both postback copy keys, and show a received AdAttributionKit copy for Apple Ads from a released build on a 26.2 or later device.
- Whether Apple Ads AdAttributionKit postbacks get second and third windows or view-through was not documented on the checked date. Confirm it from received copies before planning later-window reads.
- AdServices: `AAAttribution.attributionToken()` (iOS 14.3 and later) returns a token with a 24-hour lifetime. The server posts it as `text/plain` to `https://api-adservices.apple.com/api/v1/`. A 404 can mean the call came too soon; retry at 5-second intervals, three attempts at most. AdServices is unaffected by the postback change.
- The AdServices record includes `attribution`, `orgId`, `campaignId`, `adGroupId`, `adId`, `keywordId` (absent with search match), `claimType` (`Click` or `Impression`), `conversionType` (`Download`, `Redownload`, `PreOrder`), `countryOrRegion` and `supplyPlacement`. Campaigns with age or gender targeting return `attribution` false.
- Apple says AdServices data is retained for up to 21 days.
- The Apple Ads console, AdServices and the SKAdNetwork or AdAttributionKit postbacks apply different rules (AdServices includes view-through claims). Reconcile them; never add them.

## Google on-device conversion measurement

- For iOS App campaigns, the Google Analytics for Firebase SDK (v11.14.0 or later, iOS 12 or later) can measure conversions on the device, including from a consented email or phone number that Google says never leaves the device in identifiable form.
- It needs the `GoogleAdsOnDeviceConversion` library. Event data measurement can be turned off with `GOOGLE_ADS_ON_DEVICE_CONVERSION_EVENT_DATA_ENABLED` set to `NO`.
- Google's page does not state how it relates to ATT. The privacy owner decides whether it runs for users who declined tracking, and the decision goes in `decisions.md`.

## Practitioner rules

- Show coverage beside every iOS rate. If 60 of 100 winning first postbacks carry a fine value, 20 coarse and 20 none, and 24 of the 80 known show a purchase, report 30% among known, 24% of all, and a 24% to 44% range if every unknown could go either way. That range is a missing-value check, not a confidence interval.
- Keep `skan` and `aak` as separate values of an attribution method field so OS-driven shifts are visible.
- ATT opt-in rate counts answers, not calls, per prompt placement version:
  - Numerator: `authorized` answers. Denominator: `authorized` plus `denied` answers to a first prompt, requested while the prior status was `notDetermined` and the app was active.
  - A completion that returns `notDetermined` (an Additional Information tap or a dismissal) is not an answer. The follow-up request belongs to the same prompt, so count one answer per person.
  - EU re-prompts a year later start from `authorized` or `denied`. Report them as a separate re-prompt rate and never mix them into the first-prompt rate.
  - Report beside the rate the share of first launches whose status was already something other than `notDetermined` before the first request. Dividing by all users mixes prompt reach with acceptance.
- Prompt placement: the prompt is one-shot on every OS and in every region, and from iOS 27.2 EU users can be asked again only a year after their last answer. Place it after a value moment and never at cold start; a badly placed first prompt cannot be repaired for at least a year.
