iOS killed the pixel. Here is what attribution actually recovers
iOS did not kill attribution, it killed one path. A labelled model of what a server-side postback recovers, and what it cannot.
Every vendor in this category has a slide claiming server-side tracking recovers some large percentage of your lost conversions. I have never seen one of those slides carry a methodology, and I am not going to publish one either.
We have not measured Autonnel’s own attribution recovery rate, and we are not going to quote one until we have. The reason is structural: Autonnel is a self-hosted funnel builder. Installs do not phone home with their conversion counts, and the only party who can compute a recovery rate is the store owner, by diffing what the ad platform reports against what is in their own orders table. We do not have that data, so anything we published would be a number we made up and then dressed as evidence.
What I can do is two useful things. Explain exactly what breaks and what survives, mechanically, in terms of code you can read. And build a model, clearly labelled as a model, so you can put your own assumptions in and see how much the answer moves.
What actually breaks
Three separate failures get lumped together as “iOS killed the pixel”. They have different causes and different fixes.
Cookie lifetime. In September 2019, WebKit’s ITP 2.3 announcement stated that when a page is navigated to from a domain ITP has classified, and the landing URL carries a query string or fragment, “the expiry of persistent client-side cookies created on that page is 24 hours” (WebKit, 2019-09-23). That is a precise description of ad traffic. An ad click is a cross-site navigation from a classified domain to a URL decorated with a query parameter. So the cookie your pixel writes on landing is, in that case, a one-day cookie. Note the word client-side: this rule is about cookies created by JavaScript.
Third-party requests. In March 2020, WebKit announced full third-party cookie blocking: “Cookies for cross-site resources are now blocked by default across the board” (WebKit, 2020-03-24). Separately, and this is the part people underestimate, a pixel is a network request to a third-party host, and content blockers drop it before it is sent. When that happens no cookie policy is even involved. The event simply never leaves the device.
Click id loss on navigation. This one is self-inflicted and it is the most common. A visitor lands on /lp?fbclid=abc, clicks through to your order form at /checkout, and the click id is gone, because nothing carried it. Every step of a funnel is an opportunity to drop it.
Apple’s App Tracking Transparency, meanwhile, is the in-app permission prompt for cross-app tracking. It is the reason Google Ads issues wbraid and gbraid at all, which is documented behaviour we route on (Google Ads docs). I am not quoting an opt-in rate for it, because I have not verified one.
What the server still knows
I traced the full mechanism function by function in server-side conversion tracking, end to end, so I will not repeat it. The one-line version is that the storefront page is server-rendered, so the click id is read off the landing URL and written to your own Postgres before any JavaScript runs and before any storage policy applies to it. Routing is by click id afterwards: fbclid to Facebook, ttclid to TikTok, gclid/wbraid/gbraid to Google Ads, msclkid to Bing, one platform per order at most (ads overview, ads binding).
What matters for this post is why that changes the failure modes, and it maps onto the three above:
- Cookie lifetime stops being the binding constraint for the conversion event, because the stored touch is a row with its own expiry rather than a cookie subject to a browser’s cap. The visitor id cookie is also written server-side as a
Set-Cookieheader on the storefront response, not only bydocument.cookie, and the caps quoted above are specifically about client-side cookies. I would not assume that distinction is permanent. - Blocked third-party requests stop mattering entirely for this event, because the postback leaves your server, not the visitor’s browser. There is no request for a content blocker to drop.
- Click id loss on navigation is the one that is genuinely fixed rather than sidestepped, and it is fixed twice: the value is captured on the first page rather than the last, and click params are forwarded onto same-origin links so a multi-step funnel does not shed them.
The honest headline is narrow: the browser’s storage policy cannot reach a value that was already on your server before the browser had an opinion about it. That is the whole trick. There is no identity graph involved.
The limits, stated plainly
Four of them, and they are not small.
It does not resurrect a click id that was never in the URL. If the ad platform did not decorate the link, or the user pasted the URL without the parameter, or a redirect chain stripped it, there is nothing to capture. No click id means no postback for that platform. This is documented as a caveat rather than hidden, and it is the single biggest determinant of your real recovery rate.
It does not fix cross-device, and right now it does not even try. Someone clicks on their phone and converts on a laptop. The laptop’s landing request carries no click id, because there was no click. The usual answer is to send a hashed email so the platform can match the person instead of the click, and Autonnel does hash the buyer’s email and phone. But the payload assembler only includes those hashes on a SEND_FULL consent decision, and the payment-captured path currently passes UNKNOWN, which resolves to SEND_NON_PII. So the hashes are computed, stored on the postback, and then dropped before sending. Match quality is click-id-only until a real consent signal is wired into that call. Assume zero cross-device recovery today.
Consent can suppress it entirely. The same gate returns SUPPRESS on a denied signal, and a suppressed postback is never dispatched. Correct behaviour, and also a source of “missing” conversions that has nothing to do with Safari.
Delivered is not attributed. The stored touch expires after 30 days, and platform windows are shorter: TikTok’s default is 7 days from click, and Microsoft Advertising rejects offline conversion imports for events older than 90 days. A postback the API happily accepts can still land outside the campaign’s window and count for nothing.
A model, clearly labelled as a model
None of the following numbers are measured. They are inputs I chose to make the arithmetic legible. Substitute your own.
Take 100 paid orders that came from one platform’s clicks. Assume:
- 55% of them arrive on browsers with default tracking restrictions. Assumption, not measured.
- The browser-side pixel matches 45% of conversions on those browsers and 90% on the rest. Assumptions.
- The click id is present in the landing URL and survives to the order for 85% of them. Assumption.
- The two paths are independent within each group, and the platform deduplicates.
Browser only: 0.45 x 55 + 0.90 x 45 = 65 reported conversions.
Both paths: restricted group 1 - (0.55 x 0.15) = 0.9175, so 50.5; unrestricted 1 - (0.10 x 0.15) = 0.985, so 44.3. Total 95.
So the model says 65 becomes 95: about 30 more attributed orders per 100, a relative lift near 46%. That is the number I would be tempted to put on a slide, and here is why you should not trust it.
Change one assumption. If your click id capture rate is 60% rather than 85%, the total drops to 86 and the lift is 32%. Change a different one: if your pixel already matches 70% on restricted browsers rather than 45%, the browser-only baseline rises to 79, the combined figure to 97, and the lift falls to about 22%.
Same machinery, same code, and the answer moves between roughly +22% and +46% purely on inputs nobody has measured for your store. That range is the actual finding. Anyone quoting you a single percentage without stating those three inputs is quoting you their assumptions, not your results.
How to measure it yourself
The measurement is not hard, it is just work. Count paid orders in your own database for a window. Count what the platform reports attributed for the same window. Run one week with server-side postbacks off and one with them on, holding spend and creative as steady as you can, and diff. Then compute your click id capture rate directly, which is the input the model is most sensitive to: of your paid orders, what fraction has a stored click identifier at all.
That last query is only available to you if the orders table is yours. It is the least glamorous argument for self-hosting and probably the most concrete one, and it is a point of comparison worth applying to any tool on the shortlist. If you would rather not operate the infrastructure, Cloud pricing is published and the schema is identical.
If you do run that comparison, I would genuinely like to see it. A measured number from one real store beats a vendor’s model, including this one.