| name | analytics-privacy |
| description | Apply consent gating, data minimization, App Tracking Transparency (ATT), DMA compliance, and COPPA/GDPR rules to mobile analytics. Use when launching in the EU, UK, or US, or before enabling new telemetry. |
Analytics Privacy and Consent
Instructions
Mobile analytics touches regulation in every major market. Bake consent and minimization into the SDK layer so feature teams cannot accidentally bypass it.
1. Consent States
Model consent as an enum, not a boolean:
not_requested | granted | denied | withdrawn
And at least these categories:
crash_reporting # legitimate interest; may be on by default
performance_metrics # needs consent in EU
product_analytics # needs consent
marketing # needs consent; finer granularity (ads personalization, retargeting)
session_replay # highest sensitivity; needs explicit opt-in
Persist consent per category, with granted_at, policy_version, and source (first-run banner, settings toggle, DMA banner).
2. Apple App Tracking Transparency (ATT)
- If you use any IDFA-based attribution or cross-app tracking, you must call
ATTrackingManager.requestTrackingAuthorization before enabling it.
- Until the user responds, treat tracking as denied. IDFA is zeroed until granted.
- Facebook / Google Ads / AppsFlyer / Adjust SDKs must respect the ATT state -- check the SDK docs per version, do not assume.
- Do not show a custom pre-prompt that nudges toward "Allow" in a way that violates App Store guideline 5.1.1 (manipulative design).
ATTrackingManager.requestTrackingAuthorization { status in
Consent.set(.marketing, status == .authorized ? .granted : .denied,
source: .attPrompt)
}
3. EU DMA and GDPR
- The DMA consent banner is required in the EEA/UK for designated gatekeeper stores (Google Play, some Apple flows) and for any non-essential cookie/tracking.
- Use Google's User Messaging Platform (UMP) on Android for the TCF v2.2 consent string, or an IAB-compliant CMP on iOS.
- Store the TC string and pass it to downstream SDKs (Firebase, AdMob, Amplitude) per their documented integrations.
- Under GDPR, consent must be:
- Freely given (no "accept-or-leave" walls for non-essential data).
- Specific per category.
- Informed (link to policy).
- Unambiguous (explicit tap; no implied-by-scroll).
- Withdrawable (settings toggle must achieve the same effect as denying at first run).
4. Data Minimization
Before adding a property, ask:
- Do we already have a way to answer the same question without this field? (derived server-side)
- Can we bucket instead of storing the raw value? (age range, country, not city)
- Is it reversible / re-identifiable in combination with other fields? (browser + screen size + timezone = fingerprint)
- Can we drop it after a short window? (retention policy)
If the answer to (1) is yes, don't collect. Defaults:
- Retention: 13 months for product analytics, 6 months for logs, 90 days for session replay.
- Aggregation: all BI queries use aggregated tables, not raw event rows.
5. COPPA / Kid-Directed Apps
- If the app is directed to children under 13 (or you knowingly collect data from them):
- Disable third-party advertising SDKs.
- Disable analytics that use persistent device ids (IDFA, Android Advertising Id).
- Enable Firebase Analytics
ad_personalization_signals_enabled = false.
- Do not enable session replay.
- Google Play Families Policy and Apple Kids Category require signed DPAs and additional disclosures -- confirm with legal before ship.
6. Implementing Consent Gating at the SDK Layer
One central Consent service; no SDK init may bypass it.
object Consent {
fun can(category: Category): Boolean = state[category] == Granted
fun initializeSdks() {
if (can(Category.CrashReporting)) Crashlytics.setCollectionEnabled(true)
if (can(Category.PerformanceMetrics)) Performance.setEnabled(true)
if (can(Category.ProductAnalytics)) Mixpanel.start()
if (can(Category.SessionReplay)) SessionReplay.start()
}
fun revoke(category: Category) {
state[category] = Denied
when (category) {
Category.ProductAnalytics -> Mixpanel.reset().also { Mixpanel.disable() }
Category.SessionReplay -> SessionReplay.stopAndDropBuffered()
}
}
}
Revoking must (a) stop new collection, (b) drop buffered-but-unsent data, and (c) where feasible, trigger a backend deletion request.
7. Right to Erasure
- Provide a "Delete my data" flow in-app that:
- Calls the backend erasure endpoint.
- Resets analytics
user_id / IDFV to a new pseudonymous id.
- Clears local caches (Mixpanel profile, Amplitude cache, Sentry user context).
- Document target completion time (typically 30 days under GDPR).
8. Sensitive Event Categories
Extra care for events about:
- Health, sexuality, religion, political views -- usually special category data under GDPR; require explicit consent and separate retention.
- Children's data (see COPPA).
- Financial transactions -- emit amounts, never account numbers.
- Location -- bucket to city or larger; never emit precise GPS without consent specific to location.
9. Documentation Artifacts
Maintain:
- A Data Map listing every field collected, destination, retention, lawful basis, and owner.
- A Privacy Nutrition Label matching the App Store / Play Store disclosures. Update on every release that changes data collection.
- A DPA vendor list: every subprocessor (Firebase, Sentry, Datadog, Amplitude) must have a signed DPA.
10. Verification
- A lint rule scans the codebase for direct SDK init calls; only
Consent.initializeSdks() may touch them.
- Automated tests verify that in
denied state, no network requests with analytics headers leave the device.
- A pre-release checklist requires updated App Store / Play Store privacy disclosures.
Checklist