App Tracking Transparency (ATT)
iOS-only. When and how to prompt for ATT.
ATT is Apple's opt-in dialog for cross-app tracking. It gates IDFA access. The Layers SDK does NOT auto-prompt; you decide when.
When to prompt
Best practice: prompt when the user has seen enough value to understand why you're asking. Not on first launch.
Common timings:
- After completing onboarding (3–5 screens in).
- Before a feature that benefits from personalization.
- Alongside (or just after) a paywall.
Prompting from React Native
@layers/react-native ships a native module that wraps
requestTrackingAuthorization:
import Layers from '@layers/react-native';
await Layers.requestTracking();
// or
const status = await Layers.att.request();
// 'authorized' | 'denied' | 'restricted' | 'not_determined'Info.plist still needs the usage description. With @layers/expo the
config plugin takes it as a parameter:
["@layers/expo", {
"appId": "app_xxx",
"iosUserTrackingUsageDescription": "We use this to personalize your experience."
}]Without Expo, add it manually:
<key>NSUserTrackingUsageDescription</key>
<string>We use this to personalize your experience.</string>Status behaviors
| ATT status | idfa forwarded to Meta CAPI? | CAPI still fires? |
|---|---|---|
authorized | Yes (plaintext in madid) | Yes |
denied | No | Yes |
restricted | No | Yes |
not_determined | No | Yes |
The relay continues to forward events via IP, UA, install_id, and any
advanced-matching fields you sent; match rates just drop without IDFA.
Web / non-iOS platforms
ATT is iOS-only. Calling Layers.att.request() on other platforms is a
no-op or returns authorized / platform-equivalent values.
Native iOS SDK
There is no native Swift SDK yet, so prompting from Swift code directly
via ATTrackingManager.requestTrackingAuthorization is currently
outside the Layers SDK surface. Pass the resulting status through the
React Native or Expo SDK if you need it to round-trip to the server.
Don't ask twice
iOS only shows the prompt once per install. If you call requestTracking
after a decision has been made, the OS returns immediately without
displaying the dialog. Deep-link to Settings if the user wants to change
their mind:
import { Linking } from 'react-native';
Linking.openSettings();