Highlight
Starting with iOS 18.4, a single app can hold multiple active re-engagement conversion windows at once, with each ad recall tagged precisely by a conversion tag.
Core Content
Ad attribution has long sidestepped one problem: only one re-engagement conversion can run at any moment. Say you run two promo ads at the same time. The user taps Discount 1, opens the app, does not buy, then taps Discount 2 a few hours later, and ends up buying the Discount 1 product. Before iOS 18.3, the first conversion was already locked, and the purchase data could only attach to Discount 2. The first ad’s real value was buried. Marketers saw ROI numbers that did not match their gut, but had no place in attribution to fix it.
iOS 18.4 lifts this limit. Multiple re-engagement conversion windows can coexist, and each window carries a conversion tag — a kind of conversion bookmark. When the app sets EligibleForAdAttributionKitOverlappingConversions to YES in Info.plist, AdAttributionKit appends a query parameter to the re-engagement URL. The app reads the tag, persists it locally or on the server, and when the user actually buys, uses the tag to update the postback for that specific conversion. Attribution shifts from “who is closest to the purchase” to “which ad actually brought the user back.”
Details
The whole mechanism revolves around one query item: Postback.reengagementOpenURLParameter. After the app receives the Universal Link, it pulls the tag value out of the URL (05:42):
func retrieveConversionTag(fromURL url: URL) -> String? {
guard let components = URLComponents(url: url, resolvingAgainstBaseURL: true) else {
print("Could not get components for URL.")
return nil
}
guard let queryItems = components.queryItems else {
print("URL does not contain query items.")
return nil
}
for item in queryItems {
guard item.name == Postback.reengagementOpenURLParameter else {
continue
}
return item.value
}
return nil
}
Key points:
URLComponents(url:resolvingAgainstBaseURL:)parses the deep link; on failure it returns nil.queryItemsreturns every query parameter on the URL; if there is none, treat it as a non-recall link.- Match against
Postback.reengagementOpenURLParameter. AdAttributionKit always attaches this parameter on a re-engagement open. - Once found, return the value. That value is the conversion tag used for later updates.
After getting the tag, the business side calls the update API when the user takes a meaningful action (06:55):
func updateConversionValue(_ conversionValue: Int, conversionTag: String) async {
do {
let update = PostbackUpdate(fineConversionValue: conversionValue,
lockPostback: false,
conversionTag: conversionTag)
try await Postback.updateConversionValue(update)
}
catch {
print("An error occurred while updating the conversion value: \(error)")
}
}
Key points:
PostbackUpdatetakes three fields at once:fineConversionValue,lockPostback, andconversionTag.lockPostback: falsemeans further updates are still allowed, which fits multi-step funnels.conversionTagis the key to a targeted update. Without it, the system only updates the most recent conversion.Postback.updateConversionValueisasync throws. The caller must handle errors in a concurrent context.
The second main thread is making the attribution window and cooldown configurable. Info.plist gains a new AdAttributionKitConfigurations field that declares window lengths per ad network (10:14):
{
"AdAttributionKitConfigurations": {
"AttributionWindows": {
"com.example.adNetwork": {
"install": {
"click": 2,
"ignoreInteractionType": "view"
}
}
}
}
Key points:
- Under
AttributionWindows, the ad network identifier is the key, so each network can be configured on its own. - The
installchild describes attribution for the install type. Re-engagement has no window config, since the conversion happens right next to the interaction. clicksets the click-attribution window in days. The default is 30 days; it can be shorter or longer.ignoreInteractionTypeis set to"view"or"click", which excludes that interaction type from attribution for that network entirely.- For batch configuration, add a
globalkey as a fallback; per-network configs override the global value (10:46).
Cooldown uses the same configuration entry to express a “lockout period after a conversion” (13:52). Setting install-cooldown-hours to 6 and reengagement-cooldown-hours to 1 prevents an install conversion from being snatched by a re-engagement just minutes later.
The postback also gains a country-code field this time around (17:05). The value is an ISO 3166 two-letter country code, such as "MT". In the marketplace flow, the install verification token payload picks up a matching ccode field (16:26), so ad networks can break down budget performance by region.
Key Takeaways
-
Turn on overlapping conversions and wire up the conversion tag: Why it matters: running several re-engagements at once is the norm during promo seasons, and the old rules systematically undervalue earlier ads. How to start: add
EligibleForAdAttributionKitOverlappingConversions=YEStoInfo.plist, readPostback.reengagementOpenURLParameterin your Universal Link handler, and bind the tag to the local order context. -
Tailor the attribution window per ad network: Why it matters: a default 30-day click window makes no sense for short promotions and pulls in unrelated traffic. A shorter window makes the data reflect real buying intent. How to start: use App Analytics to see each network’s actual conversion-time distribution, then set a 2–7 day click window in
AttributionWindowsfor networks where typical conversion happens fast. For pure display networks, drop them entirely withignoreInteractionType: "view". -
Set a cooldown on critical conversion paths: Why it matters: a re-engagement intercepting an install only minutes after it completes is a real misattribution that pits your acquisition team against your recall team. How to start: look at the time gap between IAP and install, set
install-cooldown-hoursto the duration covering most first-time purchases (say, 6 hours), andreengagement-cooldown-hoursto 1 hour. Then run the AdAttributionKit test entry under iOS Settings end to end. -
Bring country-code into the postback pipeline: Why it matters: the same ROAS number means very different things in different countries. Splitting by region is the only way to find the markets that actually make money. How to start: add a
country-codefield to the postback ingest script. For the marketplace flow, also parseccodeout of the token payload as a dual record for compliance and reconciliation.
Related Sessions
- Automate your development process with the App Store Connect API — A primer on real-time push from App Store Connect Webhooks, which closes the loop with AdAttributionKit data.
- Optimize your monetization with App Analytics — The new monetization and subscription analytics, useful for reverse-engineering how long the attribution window should be.
- What’s new in App Store Connect — App Store Connect backend updates, including the entry points for attribution-related reports.
- What’s new in StoreKit and In-App Purchase — IAP and subscription API updates, the upstream source of conversion-value data.
Comments
GitHub Issues · utterances