DIAGNOSE

Why SKAdNetwork/AdAttributionKit Postback Timing Never Matches Your In-App Event Timestamps

Builtshotlingo.com
Shipped3 iOS apps: QuickBill, Travelized, Breaker
Published6 open datasets
Updated2026-08-26

Published 2026-08-26 · Updated 2026-08-26 · Cluster: analytics-reconciliation

This page is a decision tree, not a single answer. Apple's postback-based ad attribution — SKAdNetwork, and AdAttributionKit, which is replacing it — and an app's own in-app analytics (Firebase, Mixpanel, Amplitude, or a first-party event log) both claim to say when a user installed the app or converted, and structurally, by design, they cannot agree on the date. Four sections follow, each a specific, documented mechanic behind the mismatch. Work through them in order against your own numbers: each either explains the gap or rules itself out, and you move to the next.

Every mechanic below is checked against current Apple documentation, cited inline with the date checked. Where a discrepancy depends on how an attribution provider chose to model missing data, that is stated plainly, and where I could not confirm something, that is stated too.

Not sure this applies to your numbers yet? send me the two dates that disagree and I'll tell you which section below to start with.

The two records#

There is no invented example under this heading. A fabricated campaign with fabricated install counts would be exactly the unverifiable claim this site is built to avoid. Instead, here is what each source is actually built to hold, and what it leaves out.

SourceWhat it recordsWhat it leaves out
SKAdNetwork / AdAttributionKit postbackA cryptographically signed message from the device, sent after a fixed conversion window closes plus a randomized delay, reporting a coarse or fine conversion value and which of up to three windows it belongs to.Any timestamp of the original install, tap, or in-app event. The payload has no such field — only a postback-sequence-index naming which window it closed.
In-app analytics (Firebase first_open, a Mixpanel/Amplitude install or launch event)The moment the SDK actually observed the action, timestamped on-device in real time, with no window and no attribution-driven delay.Nothing structural — it is a direct client-side log. It can still be lost, delayed by the SDK's own send queue, or duplicated, but that is a device-reliability problem, not a designed-in one.

(Apple Developer, AdAttributionKit, "Identifying the parameters in a postback", checked 2026-08-26. Google Analytics Help, "[GA4] Automatically collected events", checked 2026-08-26.)

The postback was never designed to answer "when did this happen." It was designed to answer "did enough happen to report anything at all, without the timing itself identifying a person" — and that second goal is the reason the first one is unavailable by construction, covered next.

The conversion window buckets time before the delay even starts#

SKAdNetwork attributes activity to one of three fixed windows, not to a day: day 0 to 2 for the first postback, day 3 to 7 for the second, day 8 to 35 for the third (Apple Developer, StoreKit, "Receiving postbacks in multiple conversion windows", checked 2026-08-26). AdAttributionKit keeps the same shape for its own event types: a view-through ad impression has 24 hours to produce an install, a click-through ad has 30 days, and an app has 60 days after install to send the first conversion-value update at all (Apple Developer, AdAttributionKit, "Receiving ad attributions and postbacks", checked 2026-08-26).

Two installs, one on the calendar day after the campaign started and one two days later, both land in the same first window and can arrive in the same postback batch, indistinguishable from each other on the attribution side. Your in-app analytics has no such bucketing: first_open fires the first time a given user launches the app after installing, logged at that moment (Google Analytics Help, "[GA4] Automatically collected events", checked 2026-08-26). Comparing a daily install count from an attribution dashboard against a daily first_open count is comparing a three-day bucket to a one-day bucket, before any delay is even added.

What to check: whether the attribution dashboard you are reading reports by window (day 0-2, day 3-7, day 8-35) or by a single calendar date. If it is a single date, ask the provider what it represents — most model an estimated install date from the postback rather than showing the window as-is, which is its own source of disagreement, covered below.

The delivery delay is randomized on purpose, and the payload carries no clock#

Even once a window closes, the postback does not arrive immediately. The minimum elapsed time between a conversion event and the ad network receiving the postback is 24 to 48 hours for the first postback, and Apple states this explicitly as a floor, not an average (Apple Developer, AdAttributionKit, "Receiving ad attributions and postbacks", checked 2026-08-26). SKAdNetwork's own version of the same mechanic randomizes 24 to 48 hours for the first postback and 24 to 144 hours — up to six days — for the second and third (Apple Developer, StoreKit, "Receiving postbacks in multiple conversion windows", checked 2026-08-26).

RevenueCat's own SKAdNetwork guide names the reason plainly: "Apple controls the countdown timer, which has a random system to prevent advertisers from knowing the install time" (RevenueCat, "The complete guide to SKAdNetwork for subscription apps", checked 2026-08-26). If the postback arrived at a fixed, predictable offset from the real event, an ad network could correlate its own impression log against postback arrival time and re-identify who converted. The randomization is the privacy mechanism, not latency to be optimized away.

This is also why the payload has no timestamp field to fall back on. AdAttributionKit's full postback schema — postback-identifier, ad-network-identifier, source-identifier, postback-sequence-index, conversion-type, did-win, conversion-value and coarse-conversion-value — contains no field for when the underlying install, tap, or conversion actually happened (Apple Developer, AdAttributionKit, "Identifying the parameters in a postback", checked 2026-08-26). postback-sequence-index tells you which of the three windows produced it. Nothing in the message tells you where inside that window, or how long the random delay held it back.

What to check: whether your attribution provider's dashboard date is the postback's actual arrival date or a modeled "estimated install date" that subtracts an assumed window length and average delay from it. RevenueCat's own guidance for exactly this problem is to stop trying to read a daily figure at all: "there's no way to connect the dots between different postback sequences," and it recommends working in an "Estimated Install Week" instead of a day (RevenueCat, "The complete guide to SKAdNetwork for subscription apps", checked 2026-08-26). If two attribution tools disagree with each other and not just with your analytics, one is very likely showing arrival date and the other an estimate, and that is a definitional gap, not a data-quality one.

Crowd anonymity can withhold the conversion value entirely, with nothing to reconcile against#

The first three sections explain when a postback shows up. This one explains why it can show up with no usable data at all, on a campaign small enough that your in-app analytics logged every event correctly.

Apple assigns every download a postback data tier "to ensure crowd anonymity" — the fewer conversions behind a given campaign and app, the less specific the data Apple will release in the postback for it (Apple Developer, StoreKit, "SKAdNetwork", checked 2026-08-26). In practice this is a four-tier system, 0 through 3: the top tiers release a fine-grained conversion value (a 6-bit number, 0 to 63) in the first postback, a lower tier releases only a coarse value of "low," "medium" or "high," and the postback for the second and third windows drops to coarse-only regardless of tier (Apple Developer, StoreKit, "Receiving postbacks in multiple conversion windows", checked 2026-08-26; Apple Developer, StoreKit, "SKAdNetwork 4 release notes", checked 2026-08-26). RevenueCat describes the floor of that scale directly: below the threshold, "SKAN will report all the activity, but with the condition that the conversion value parameter in the payload is set to null" (RevenueCat, "The complete guide to SKAdNetwork for subscription apps", checked 2026-08-26).

No such threshold exists on the analytics side. A first-party SDK logs the same event whether one user or ten thousand triggered it that day; nothing about its own row count is suppressed to protect a crowd. So a small campaign can show real, complete conversion data in your analytics tool and a null or coarse-only value in the attribution dashboard for the identical population — not a timing gap this time, but a population-size gap that happens to look like one if you are only watching the numbers, not the tier.

What to check: whether the campaign or ad network in question is large enough, in your account, to consistently clear the top tier. A provider dashboard that shows conversion values reliably for one campaign and blank or null for a smaller one is very likely reporting tier, not a broken integration.

Which one is right#

There is no single right one; each answers a different question, and reading the wrong one for the question you are actually asking is the real failure mode here, not any individual mechanic above.

For anything that needs a real, individual timestamp — cohort analysis, funnel timing, a specific user's session history — use in-app analytics. It is the only one of the two that ever recorded a clock reading in the first place.

For attributing an install or a conversion to a specific ad campaign under Apple's privacy model — the only lawful way to do that attribution on iOS at all since ATT — use the postback, but read it at the window or week level it was actually designed to report at, never as a daily time series, and know which of "raw arrival date" or "modeled estimated date" your attribution provider is showing you before you compare it to anything else.

If what disagrees for you is user or session counts between Firebase and its own BigQuery export, rather than attribution timing, that is a different set of causes with its own decision tree: why Firebase Analytics and its BigQuery export disagree. And if the postback's conversion-value payload is being used to infer subscription status rather than just install attribution, the more direct mismatch to check first is between the device and your receipt-validation backend: why StoreKit 2's currentEntitlements and your receipt-validation backend disagree about who's subscribed.

Get this checked for your app#

If you've worked through the sections above and the gap still doesn't add up, that's what the diagnostic below is for.

Send me your numbers and I'll tell you which of the causes above is yours.

[ 01 ] Get the next one

One article when the next one ships. No spam, unsubscribe anytime.

[ 02 ] Get this done

Send the product URL and what looks wrong. A real reply, not an autoresponder.