Skip to main content

Privacy & Consent Guide

info

Most apps need no privacy API calls at all. If you use a standards-compliant CMP, the SDK already reads its output. This page covers that default, the iOS tracking prompt you do have to call, and the override API for custom CMPs.

Overview​

The SDK reads privacy signals from platform storage rather than from arguments you pass in, so consent applies uniformly to every placement — including the Dart-implemented Feed, which asks the native SDK rather than deriving consent itself.

Three things are worth separating:

ConcernWhat you do
GDPR / CCPA / GPP consentNothing, if your CMP is standards-compliant
iOS tracking permission (ATT)Call TeadsPrivacy.requestTrackingAuthorization()
Custom or server-driven consentSet overrides via TeadsPrivacy

A standards-compliant CMP writes the IAB keys into UserDefaults (iOS) and SharedPreferences (Android):

StandardKey
TCF v2 (GDPR)IABTCF_TCString
TCF v1 (GDPR, legacy)IABConsent_ConsentString
US Privacy (CCPA)IABUSPrivacy_String
GPPIABGPP_HDR_GppString

The SDK reads them from there. Nothing to call, nothing to pass.

tip

Show your CMP before creating placements. Signals are read when a placement assembles its ad request, so a consent decision made afterwards does not apply to ads already requested.

iOS App Tracking Transparency​

On iOS 14.5+ you must ask before the IDFA is available. This is the one privacy call almost every app needs:

// iOS only. Returns null on Android.
final bool? authorized = await TeadsPrivacy.requestTrackingAuthorization();

Requirements​

  1. NSUserTrackingUsageDescription in Info.plist. Without it, iOS denies the request outright instead of prompting.

    <key>NSUserTrackingUsageDescription</key>
    <string>Allows us to show you more relevant ads.</string>
  2. Call it after your first frame is on screen, and before creating placements.

Never await ATT before runApp

On a physical device the ATT completion handler never fires while there is no UI on screen — the app hangs on a white screen indefinitely. The iOS Simulator does not reproduce this, so it reaches production easily.

Request it from your first screen instead:

class _HomeScreenState extends State<HomeScreen> {

void initState() {
super.initState();
WidgetsBinding.instance.addPostFrameCallback((_) async {
await TeadsPrivacy.requestTrackingAuthorization();
});
}
}

Reading the return value​

ValueMeaning
trueThe user authorized tracking; the IDFA is available
falseThe user declined
nullAndroid — there is no ATT prompt, so the question does not apply

null means "not applicable", not "denied". Do not treat them the same.

If you never call it​

iOS reports tracking as unauthorized, the SDK withholds the IDFA, and every placement requests without one. That is correct behavior, not a bug — but it does mean no advertising identifier anywhere in the SDK, which affects fill and revenue.

Checking the current status​

To read the current authorization state without prompting:

final bool? authorized = await TeadsPrivacy.isIdfaTrackingAuthorized;

This never triggers the dialog. It returns null on Android.

Use TeadsPrivacy only when platform storage is not the source of truth: a custom CMP that does not write the IAB keys, a server-driven consent decision, or QA forcing a reject case no CMP on the device would produce.

An override wins over the stored IAB value for as long as it is set, on both platforms.

// GDPR — IAB TCF
await TeadsPrivacy.setGdprApplies(true);
await TeadsPrivacy.setGdprConsentStringV2('CPXxRfAPXxRfAAfKABENB-CgAAAAAAAAAAYgAAAAAAAA');
await TeadsPrivacy.setGdprConsentStringV1('BOEFEAyOEFEAyAHABDENAI4AAAB9vABAASA');

// US Privacy (CCPA)
await TeadsPrivacy.setUsPrivacyString('1YNN');

// Global Privacy Platform
await TeadsPrivacy.setGppString('DBABMA~CPXxRfAPXxRfAAfKABENB-CgAAAAAAAAAAYgAAAAAAAA');
await TeadsPrivacy.setGppSections('2');

Clearing overrides​

Returns the SDK to reading platform storage:

await TeadsPrivacy.clearAllOverrides();

Complete example​

class ConsentService {
/// Call after your CMP resolves, and before creating any placement.
Future<void> applyConsent(MyConsentState state) async {
if (!state.hasCustomCmp) {
// A standards-compliant CMP has already written the IAB keys.
// Nothing to do — do not set overrides you cannot keep accurate.
return;
}

await TeadsPrivacy.setGdprApplies(state.gdprApplies);
await TeadsPrivacy.setGdprConsentStringV2(state.tcfString);

if (state.usPrivacyString != null) {
await TeadsPrivacy.setUsPrivacyString(state.usPrivacyString);
}
if (state.gppString != null) {
await TeadsPrivacy.setGppString(state.gppString);
await TeadsPrivacy.setGppSections(state.gppSectionIds);
}
}

Future<void> onConsentWithdrawn() => TeadsPrivacy.clearAllOverrides();
}
An override persists until you clear it

Set an override and the SDK stops reading your CMP's stored value for that signal. If the user later changes their choice in your CMP, you must push the new value or call clearAllOverrides() — otherwise a stale decision keeps applying.

What the SDK Deliberately Does Not Expose​

The native SDKs have a setIDFAConsent method that overrides the ATT status. It is not surfaced in Flutter, on purpose: a build carrying it reports tracking consent the real ATT flow never granted, which makes an untested path look tested. Request ATT for real via TeadsPrivacy.requestTrackingAuthorization() instead.

User Identifiers​

FeedPlacementConfig and BannerPlacementConfig accept an optional userId for personalized recommendations.

warning

Only supply userId if you have consent to do so and your use complies with GDPR, CCPA and any other applicable regulation. It is optional — omit it if in doubt.

Privacy Checklist​

  • CMP shown before any placement is created
  • NSUserTrackingUsageDescription present in Info.plist
  • requestTrackingAuthorization() called after the first frame, before placements
  • null from ATT handled as "Android", not "declined"
  • Overrides used only if you have a custom CMP
  • Overrides refreshed or cleared when the user changes their choice
  • userId supplied only with consent
  • Full-reject consent case tested end to end
  • App privacy details on the App Store / Play Console reflect advertising data use

Testing Privacy Compliance​

Turn up SDK logging while testing consent flows:

TeadsLog.level = LogLevel.debug;

See Logging.

To test a rejection path, set an override that represents no consent, exercise the placements, then clearAllOverrides() to return to your CMP's real value.

Additional Resources​

Support​

Privacy questions specific to your setup: contact your Partner Manager. See Support.


tip

Pro tip: the correct integration for most apps is a CMP plus one ATT call. If you find yourself setting many overrides, check whether your CMP already writes the IAB keys — it probably does.