Skip to main content

ATT and IDFA (iOS 14+)

On iOS 14+, the IDFA isn’t collected automatically — Apple requires explicit user permission via App Tracking Transparency (ATT). After receiving the user’s choice, pass the status to the SDK so it can attach att_authorization_status (and optionally advertising_id) to event payloads.

Option 1 — SDK reads IDFA from the device

import AppTrackingTransparency
import AdSupport
import ZeotapCollect

ATTrackingManager.requestTrackingAuthorization { status in
  Collect.getInstance()?.setAdvertisingIdByATTStatus(status: status)
}
The SDK reads the IDFA via ASIdentifierManager when status is .authorized. For other statuses, only att_authorization_status is attached.

Option 2 — explicit status + IDFA

Useful when your app already has the IDFA in hand:
ATTrackingManager.requestTrackingAuthorization { status in
  switch status {
  case .authorized:
    Collect.getInstance()?.setATTStatusAndAdvertisingId(
      status: status,
      advertisingId: ASIdentifierManager.shared().advertisingIdentifier.uuidString
    )
  case .denied, .notDetermined, .restricted:
    Collect.getInstance()?.setATTStatusAndAdvertisingId(status: status)
  @unknown default:
    break
  }
}
For iOS 13 and below the SDK collects IDFA automatically — no ATT call needed. att_authorization_status values match Apple’s enum: authorized, denied, not_determined, restricted. See Apple’s ATT documentation.

Identity patterns

Tie post-login behavior to a known user

When the user logs in:
Collect.getInstance()?.setUserIdentities([
  "email": "user@example.com",
  "loginid": "userA-123",
  "fpuid": "crm_42"
])

Collect.getInstance()?.setEventProperties("login", ["method": "password"])
All subsequent events carry those identities.

Merge unknown and known users

Use setUserProperties (not setUserIdentities) to attach additional attributes to the same zi:
Collect.getInstance()?.setUserProperties([
  "plan": "premium",
  "country": "DEU"
])

Differentiate two users on the same device

setUserIdentities ties events to the current identity until you unset:
Collect.getInstance()?.setUserIdentities(["email": "alice@example.com"])
// ... Alice's activity

Collect.getInstance()?.unsetUserIdentities()
Collect.getInstance()?.setUserIdentities(["email": "bob@example.com"])
// ... Bob's activity

Send identities only after the SDK hashes them

let opts = CollectOption()
  .writeKey(value: "YOUR_WRITE_KEY")
  .areIdentitiesHashed(value: false)
  .hashIdentities(value: true)
  .build()

Collect.getInstance()?.setUserIdentities([
  "email": "xyz@gmail.com",  // hashed by SDK
  "crmID": "12345"           // custom identity, sent as-is
])

Send identities that are already hashed

let opts = CollectOption()
  .writeKey(value: "YOUR_WRITE_KEY")
  .areIdentitiesHashed(value: true)
  .build()

Collect.getInstance()?.setUserIdentities([
  "email_sha256_lowercase": "32e19a491662fd86de7d3806b1199b76f0ee44e928d3475f05b0c8a59912c097",
  "crmID": "12345"
])

Common payloads

Add to cart

Collect.getInstance()?.setEventProperties("addToCart", [
  "productID": "sku_123",
  "quantity": 1,
  "price": 29.99
])

Purchase

Collect.getInstance()?.setEventProperties("purchase", [
  "orderID": "order_456",
  "total": 89.97,
  "currency": "EUR"
])

Logout

Collect.getInstance()?.unsetUserIdentities()

Set events with a callback

All event methods accept an optional ResponseCallback. Use it to confirm dispatch or surface errors in dev:
Collect.getInstance()?.setEventProperties("Add_to_cart") { data in
  print("Status: \(data.status), Message: \(data.message)")
}
See SDK Reference for the callback contract.

Working with zi

The SDK generates a zi per app install per write key. Override or reset it for advanced flows:
// Get current zi
let zi = Collect.getInstance()?.getZI()

// Reset zi (e.g. on logout when you want a fresh identity)
Collect.getInstance()?.resetZI()

// Override zi (e.g. use your own hashed loginid)
Collect.getInstance()?.setZI(zi: "your-hashed-loginid")

Pause and resume collection

Useful for screens where you don’t want background events:
Collect.getInstance()?.pauseCollection()
// ... no events sent in this window
Collect.getInstance()?.resumeCollection()

Contextual data Zeotap captures automatically

You don’t need to send these explicitly — the SDK appends them to every event:
  • app_name, app_version
  • device_model_identifier, os_type, os_version
  • advertising_id (with ATT-derived att_authorization_status on iOS 14+)
  • network_type, carrier
  • country (IP-derived or from userCountry override)
  • zi (SDK-generated user ID)
Last modified on June 22, 2026