A Privacy-First Attribution Stack: Reference Architecture
A privacy-first attribution stack has three tiers: deterministic where the user consented, aggregated where the OS forces it, and modeled on top to cover the rest. The catch nobody puts on the intro slide is that the aggregated layer now lives on iOS only, through AdAttributionKit and SKAdNetwork, because Google retired the Android Attribution Reporting API in October 2025. Any blueprint that assumes symmetric OS support is already wrong.
The postback that lied
A gaming client, mid-2024. One user-acquisition campaign showed clean SKAN conversion values week after week, with a coarse value of "high" on roughly 8% of installs and a decent-looking CPI. Their internal dashboard was happy.
I was not.
We ran a geo holdout against it: two matched regions, one exposed, one dark, four weeks. The incremental lift on day-7 revenue came back at roughly zero. Not "small." Flat — the confidence interval straddled the axis.
So here was the situation. A campaign producing "measurable" SKAN postbacks that, when you actually withheld the ads, changed nothing. The postbacks were real, the attribution was real, and yet the lift was fictional. Someone was serving cheap inventory to users who'd have installed anyway, harvesting the last click, and the OS-aggregated layer dutifully confirmed a story that never happened.
We reallocated. About 22% of that channel's monthly spend moved to channels where holdouts showed positive lift. On a mid-six-figure monthly budget, the recovered waste worked out to a low-five-figure refund from the network after we presented the holdout data — call it 220 basis points of total UA spend that had been buying nothing.
That's the thesis. What a stack can measure is not the same as what happened, and you should design accordingly.
Why the OS split forces three layers
The whole architecture is shaped by one fact: the two platforms no longer behave the same way, and pretending otherwise builds you a stack that reports confidently on data it doesn't have.
On iOS, Apple moved past SKAdNetwork into AdAttributionKit, its crowd-anonymity framework that launched with iOS 17.4. Apple's own developer documentation describes click-through attribution as requiring an install within 30 days of the ad click. Apple added re-engagement handling in iOS 18, then at WWDC25 layered in configurable attribution windows, cooldowns, overlapping re-engagement conversion windows, and country-level geo in postbacks, all rolling out with iOS 18.4.
Android went the other way. On October 17, 2025, Google announced it would retire the Privacy Sandbox Attribution Reporting API for both Chrome and Android, listing it alongside Topics, Protected Audience and others as technologies being wound down. The reason was adoption. PPC Land, reporting on the shutdown, noted CMA testing had shown a roughly 30% publisher revenue decline during trials. That's the kind of number that kills a standard.
So one platform has a maturing aggregated layer and the other has an empty slot. That asymmetry is the design constraint, not a footnote.
What "privacy-first attribution" actually means
Privacy-first attribution is not the absence of tracking. It's a discipline: use deterministic, user-level signal only where the user consented, accept OS-mandated aggregation where you have no choice, and model the gap you can't observe directly. Three postures, applied per data source and per platform. Anything that treats one posture as the whole answer is either lying to you or about to.
The reference architecture
Picture four horizontal bands stacked bottom to top. Data flows upward, and each band feeds the one above it with a different grade of certainty.
Layer 3 MODELING & INCREMENTALITY (MMM + geo/holdout lift)
^ reconciles / arbitrates everything below
Layer 2 OS-AGGREGATED POSTBACKS (AdAttributionKit / SKAN — iOS only)
^ Android side of this band is empty
Layer 1 DETERMINISTIC CROSS-PROPERTY (consented IDFA, ~35-50% coverage)
^ feeds user-level truth where opt-in exists
Layer 0 FIRST-PARTY EVENT SPINE (your SDK, deep links, deferred links)
^ deterministic inside your own product, always
The arrows matter more than the boxes. Layer 0 is the only place you own complete, deterministic truth, because it's your own product and your own events. Layer 1 enriches Layer 0 with cross-property identity, but only for consented users, so the arrow upward is thin. Layer 2 delivers aggregated postbacks that never join cleanly to the layers below by design, and the arrow into Layer 3 is dotted, because you're handing modeling a signal it must treat with suspicion. Layer 3 reaches back down, reconciles all three, and hands you a spend decision.
Read the stack top-down and it looks like a hierarchy. Read it bottom-up and it's a confidence gradient. Both readings are correct.
Layer 0 — first-party event spine
This is deterministic bedrock inside your product. Every install, session, purchase, and funnel step you record through your own SDK is user-level truth, because the user is inside an app you control. No consent prompt gates your own product analytics.
Deep links and deferred deep links live here too, stitching pre-install intent to post-install behavior. A user taps a link, doesn't have the app, installs, and the deferred deep link routes them to the intended screen after first launch. That stitch is first-party, and it survives the privacy changes because it never depended on a cross-app identifier in the first place.
Build this layer first. If Layer 0 is thin, everything above it is guessing.
Layer 1 — deterministic, consent-bounded
User-level IDFA data is alive, but only for the fraction of users who opted in. AppsFlyer's April 2025 four-year ATT analysis put global opt-in at roughly 50%, framing it as a meaningful climb over the initial rollout. Adjust panels, as summarized by AppFollow in late 2025, land nearer 35% blended, with large variance by vertical and country.
Both can be true, since prompt strategy and region swing the number hard. The practitioner takeaway is the same either way: treat consented user-level data as a high-value partial signal, never the foundation. If half your users are invisible on your best day, a stack that assumes user-level completeness will over-attribute the visible half and under-count everything else.
Layer 2 — OS-aggregated postbacks
Here's where iOS carries the whole band and Android contributes nothing.
AdAttributionKit's mechanics, per Apple's WWDC24 session, are governed by crowd anonymity. The source identifier carries a minimum of 2 and a maximum of 4 digits, and crowd anonymity determines how many of those digits actually appear, while at low volumes the publisher identifier is omitted entirely. So the granularity of what you receive is a function of how many other conversions look like yours, and Apple does not publish the thresholds.
The iOS 18.4 additions from WWDC25 help. Apple's session notes that apps can now hold multiple active re-engagement conversion windows simultaneously and receive conversion tags, which act like a bookmark for a conversion. Configurable windows, cooldowns, and country-level geo came in the same release. More levers, same anonymity gate.
Underneath sits SKAdNetwork 4.0, available since iOS 16.1. It introduced hierarchical source identifiers, coarse conversion values that appear only when privacy thresholds are met, and up to three postbacks to observe engagement over time. AdAttributionKit builds on those fundamentals.
Android note, and it's the whole story: this layer returns nothing. The ARA is retired. If your architecture diagram shows an Android aggregated box, delete it.
Layer 3 — modeling and incrementality
This layer exists because Apple and Google won't let you measure what you actually want to know. Marketing mix modeling gives you top-down channel contribution without user-level data, while geo holdouts and lift tests give you causal answers by withholding ads and watching what changes.
Modeling isn't a nice-to-have bolted on at the end. It's the arbiter. Go back to the cold open: the SKAN postbacks looked clean, and only the holdout revealed the lift was zero. Layer 3 is the layer that catches Layer 2 lying to you. Without it, you're trusting aggregated signals with no way to falsify them.
Reconciling three numbers that disagree
Take one iOS campaign, one week. Round numbers so the arithmetic is visible.
- Layer 1, deterministic (consented ~50%): you observe 500 attributed installs among opted-in users. Naive scale-up to full population implies ~1,000.
- Layer 2, SKAN/AdAttributionKit: aggregated postbacks credit the campaign with 1,300 conversions over the 30-day window.
- Layer 3, modeled incremental estimate: your geo holdout says the campaign drove ~700 incremental installs.
Three numbers, all "correct" within their own frame, and none of them agree.
Reconcile top-down. The modeled 700 is your causal anchor, because it's the only figure derived by actually withholding the ad. That SKAN 1,300 includes organic installs the campaign harvested via last touch, users who'd have converted regardless. So the gap between aggregated-attributed (1,300) and incremental (700) is 600 installs of over-attribution, or about 46% of the credited volume.
Now price it. Say your effective cost per credited install was $4, and you were budgeting against the SKAN number. You paid $5,200 for 1,300 credited installs, but only 700 were incremental. The over-credited 600 represent roughly $2,400 of spend justified by a signal modeling couldn't confirm — about 46% of the campaign's attributed conversions you shouldn't have trusted at face value.
You don't cut the whole campaign. You reweight your bid targets to the incremental number and re-test. That's the loop.
Where each layer gets built
No single vendor cleanly owns all four layers, and anyone claiming to should be read skeptically. I'll use the same rubric for each: which layer it serves, what kind of signal, one honest strength, and one maturity caveat.
| Vendor / component | Layer served | Signal type | Notable strength | Maturity caveat |
|---|---|---|---|---|
| AppsFlyer | Layer 2 + panels | Aggregated + deterministic | Long-run ATT opt-in data (their April 2025 four-year analysis) and mature SKAN handling | MMP-class; still bounded by the same crowd-anonymity limits everyone faces |
| Adjust | Layer 2 + panels | Aggregated + deterministic | Panel opt-in benchmarking (~35% blended, per AppFollow's summary) | Panel figures vary by methodology; not a substitute for your own holdouts |
| Kixo | Layer 0 | First-party deterministic + deep links | First-party product analytics (events, funnels, retention, cohorts), session replay, and deferred deep links via kixo.cc short links, with a chat-first AI dashboard | Kixo is a first-party/product-analytics and deep-link layer, not an OS-aggregated MMP; it does not handle or replace SKAN/AdAttributionKit postback processing |
Kixo (kixo.io) sits at Layer 0, the event spine. Its SDKs for iOS, Android, and Web instrument your product, and your team queries the dashboard in plain language and gets charts back with a visible reasoning trail. For the deep-link stitch — deferred routing from a pre-install tap to a post-install screen — that's the band it belongs in. Just don't file it under the aggregated-postback layer, because that's not what it does, and treating a first-party tool as a SKAN replacement is how architectures quietly break.
If you're weighing measurement approaches more broadly, the tradeoffs between modeled and deterministic measurement are worth reading before you commit budget to one posture.
What you still can't measure
Be honest about the holes.
Crowd-anonymity thresholds are undisclosed. Apple tells you the source identifier runs 2 to 4 digits and that anonymity decides how many you get, but not the volume you need to hit each tier. You can't plan campaign structure around a gate you can't see.
The Android aggregated view is vaporware. Not "limited" — gone since October 2025. Anyone selling you an Android SKAN-equivalent dashboard is selling you a model dressed as a measurement.
Deterministic coverage tops out around half your users on iOS, and closer to a third by some panels, which means the remainder are permanently below your user-level resolution.
And incrementality itself has limits. Holdouts need scale and clean geos, so small campaigns produce confidence intervals too wide to act on. You'll have channels where you genuinely can't prove lift either way, and the correct answer there is "we don't know," not a fabricated number.
Build order
Stand it up in this sequence, because each layer depends on the one below being real first.
- Instrument Layer 0. Your own events, funnels, retention, and deep-link stitching. If your first-party spine is incomplete, nothing above it can be trusted.
- Add consented deterministic (Layer 1). Wire up ATT-consented user-level data, and set your expectation at roughly 35–50% coverage depending on your vertical and prompt, per the AppsFlyer and AppFollow-summarized Adjust figures.
- Wire iOS aggregated postbacks (Layer 2). AdAttributionKit and SKAN handling, with explicit awareness that Android contributes nothing to this band.
- Stand up modeling and incrementality (Layer 3) as the arbiter. MMM plus geo holdouts — the layer that overrules the others when they disagree.
For teams still deciding how much to invest in the incrementality layer versus richer deterministic tooling, the practical case for prioritizing holdout testing lays out the sequencing argument in more depth.
The stack's job isn't to reclaim the precision the OS took away. That precision is gone, and no vendor is getting it back. The job is narrower and more useful: stop trusting any signal that modeling can't confirm. My gaming client learned that at a cost of 220 basis points of wasted spend. Build the arbiter early, and you learn it for the price of one holdout instead.