Privacy & Consent Guide
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:
| Concern | What you do |
|---|---|
| GDPR / CCPA / GPP consent | Nothing, if your CMP is standards-compliant |
| iOS tracking permission (ATT) | Call TeadsPrivacy.requestTrackingAuthorization() |
| Custom or server-driven consent | Set overrides via TeadsPrivacy |
Automatic Consent Detection
A standards-compliant CMP writes the IAB keys into UserDefaults (iOS) and
SharedPreferences (Android):
| Standard | Key |
|---|---|
| TCF v2 (GDPR) | IABTCF_TCString |
| TCF v1 (GDPR, legacy) | IABConsent_ConsentString |
| US Privacy (CCPA) | IABUSPrivacy_String |
| GPP | IABGPP_HDR_GppString |
The SDK reads them from there. Nothing to call, nothing to pass.
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
-
NSUserTrackingUsageDescriptioninInfo.plist. Without it, iOS denies the request outright instead of prompting.<key>NSUserTrackingUsageDescription</key><string>Allows us to show you more relevant ads.</string> -
Call it after your first frame is on screen, and before creating placements.
runAppOn 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
| Value | Meaning |
|---|---|
true | The user authorized tracking; the IDFA is available |
false | The user declined |
null | Android — 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.
Consent Overrides for Custom CMPs
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();
}
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.
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
-
NSUserTrackingUsageDescriptionpresent inInfo.plist -
requestTrackingAuthorization()called after the first frame, before placements -
nullfrom 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
-
userIdsupplied 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.
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.