Layers

App Tracking Transparency (ATT)

iOS-only. When and how to prompt for ATT.

View as Markdown

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 statusidfa forwarded to Meta CAPI?CAPI still fires?
authorizedYes (plaintext in madid)Yes
deniedNoYes
restrictedNoYes
not_determinedNoYes

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();

On this page