ANTIGRAVITY LABJP
Articles/Integrations
Integrations/2026-05-23Advanced

Antigravity × UMP/ATT Consent Rate Optimization Agent — A Weekly Autonomous Loop That Lifts AdMob Revenue Region by Region

ATT and Google UMP consent rates are hidden levers that move AdMob eCPM by 1.3–2.0x. This is a working memo on letting Antigravity sub-agents run weekly experiments on regional consent UX across six apps, and how that lifted ARPDAU.

antigravity429admob17umpatt2consentmonetization31agents123

Premium Article

Over the last couple of months I went back through the consent UX of all six apps I currently run, both the iOS ATT (App Tracking Transparency) flow and the Google UMP (User Messaging Platform) flow. The reason was almost embarrassingly simple: when I lined up my AdMob monthly report by country, a ten-point swing in consent rate was moving eCPM by 1.3x to 2.0x. The lever was right there in front of me.

I started building apps independently in 2014, my titles have crossed 50 million downloads cumulatively, and AdMob still pays the bills. When I first touched the internet in 1997 at sixteen, a stranger on a forum overseas read my code and shipped me back a fix — that small moment of "we can build across borders" still quietly shapes how I think about consent UX today. A consent prompt is not just a legal checkbox. It is the front door of a relationship with a user in a specific region, and if the door is rude, everything downstream — retention, ARPDAU, reviews — drags.

This article is a working memo on how I let Antigravity sub-agents run a weekly observe → hypothesize → implement → verify loop on consent UX, where I draw the line between autonomous and human-in-the-loop, and which mistakes I personally walked into. It is not a guide to integrating the SDKs or to writing the legal copy. It assumes you already have UMP and ATT in place and want to keep improving them without doing it ad hoc.

Why consent rate deserves to be a top-level AdMob KPI

When a user denies ATT, no IDFA is shared and AdMob serves NPA (non-personalized ads). When a UMP user in the EEA refuses, same outcome. NPA eCPM lands somewhere around 0.5x to 0.7x of personalized eCPM based on my own apps and the public benchmarks I've seen. Lifting consent from 60% to 80% in a given country roughly translates to a 10–15% lift in that country's ad revenue. In the country that improved the most for me, the monthly AdMob revenue came out about 12% higher.

The number itself is technically easy to measure. ATT exposes ATTrackingManager.trackingAuthorizationStatus and UMP exposes consentStatus on ConsentInformation; you just log the final state. The hard part is what to compare against and whether you keep comparing. I have twice run a one-off improvement, walked away pleased, and come back six months later to find the rate had slipped eight points because of an OS release or a region rollout. That is what pushed me to treat consent rate as a seasonally variable KPI and to put Antigravity in charge of watching it every week.

Step 1: event schema and BigQuery export

The first job is shaping events at a granularity that a sub-agent can actually use. If you skimp here, no amount of clever prompting downstream will save you. My apps emit the events below through Firebase Analytics and push them to BigQuery.

// Android (Kotlin) — log the settled UMP state
private fun logUmpStatus(status: Int, geo: String) {
    val statusLabel = when (status) {
        ConsentInformation.ConsentStatus.OBTAINED     -> "obtained"
        ConsentInformation.ConsentStatus.REQUIRED     -> "required"
        ConsentInformation.ConsentStatus.NOT_REQUIRED -> "not_required"
        ConsentInformation.ConsentStatus.UNKNOWN      -> "unknown"
        else -> "other"
    }
    firebaseAnalytics.logEvent("ump_status_settled") {
        param("status", statusLabel)
        param("geo", geo)                  // ISO-3166 alpha-2
        param("os_major", Build.VERSION.SDK_INT.toString())
        param("app_version", BuildConfig.VERSION_NAME)
        param("prompt_variant", currentVariantId)  // from Remote Config
    }
}
// iOS (Swift) — log the settled ATT state
import AppTrackingTransparency
import FirebaseAnalytics
 
func logAttStatus(geo: String) {
    let status = ATTrackingManager.trackingAuthorizationStatus
    let label: String
    switch status {
    case .authorized:    label = "authorized"
    case .denied:        label = "denied"
    case .restricted:    label = "restricted"
    case .notDetermined: label = "not_determined"
    @unknown default:    label = "other"
    }
    Analytics.logEvent("att_status_settled", parameters: [
        "status": label,
        "geo": geo,
        "os_major": String(ProcessInfo.processInfo.operatingSystemVersion.majorVersion),
        "app_version": Bundle.main.infoDictionary?["CFBundleShortVersionString"] ?? "",
        "prompt_variant": currentVariantId
    ])
}

I personally prefer suffixing the event with _settled. A prompt_shown style event tends to drift away from the final outcome, and downstream aggregation gets messy. The geo field uses the region the SDK estimates, not Locale.current. More on why later — I once miscategorized travelers using device locale and it hurt.

On the BigQuery side I build a single normalized view. Sub-agents should never touch raw events; that way the event schema can change underneath without breaking the agent layer.

-- BigQuery: weekly consent rate aggregated by region × OS × variant
CREATE OR REPLACE VIEW analytics.consent_rate_weekly AS
SELECT
  DATE_TRUNC(DATE(event_timestamp), WEEK(MONDAY)) AS week_start,
  app_id,
  platform,                                    -- ios / android
  geo,
  os_major,
  prompt_variant,
  COUNTIF(event_name IN ('att_status_settled','ump_status_settled')) AS settled,
  COUNTIF(event_name = 'att_status_settled' AND status = 'authorized') AS att_yes,
  COUNTIF(event_name = 'att_status_settled' AND status = 'denied')     AS att_no,
  COUNTIF(event_name = 'ump_status_settled' AND status = 'obtained')   AS ump_yes,
  COUNTIF(event_name = 'ump_status_settled' AND status IN ('required','unknown')) AS ump_no
FROM analytics.events
WHERE _PARTITIONDATE >= DATE_SUB(CURRENT_DATE(), INTERVAL 90 DAY)
GROUP BY 1,2,3,4,5,6;

Thank you for reading this far.

Continue Reading

What follows includes implementation code, benchmarks, and practical content we hope you'll find useful. This site runs without ads — server and development costs are supported entirely by members like you. If it's been helpful, we'd be truly grateful for your support.

WHAT YOU'LL LEARN
Event schema for normalizing UMP/ATT consent state across region × OS into BigQuery, in a shape Antigravity sub-agents can actually reason over
How to split a weekly experiment loop into four sub-agents — observer, hypothesis-writer, variant-implementer, result-judge — with a single human-approval gate
Real production pitfalls: trying to re-prompt ATT, initializing AdMob before UMP, mis-detecting EU users by Locale, and missing the consent-withdrawal entry point in the EEA
Secure payment via Stripe · Cancel anytime

Unlock This Article

Get full access to the rest of this article. Buy once, read anytime. This site is ad-free — your support goes directly toward keeping it running.

or
Unlock all articles with Membership →
Share

Thank You for Reading

Antigravity Lab is ad-free, supported entirely by members like you. We publish practical guides daily with implementation code, benchmarks, and production-ready patterns. If you've found it useful, we'd love to have you on board.

  • Copy-paste ready implementation code
  • New advanced guides published daily
  • $5/mo or $10 for lifetime access
View Membership →

Related Articles

Integrations2026-05-19
Running AdMob and AppLovin MAX side by side with an Antigravity sub-agent that compares them daily
For the past two months I have been running my wallpaper apps on both AdMob and AppLovin MAX in parallel, and letting Antigravity sub-agents pull eCPM, fill rate and ARPDAU into a single daily comparison. This is an implementation memo from those 90 days, focused on cross-network normalisation and the thresholds I trust an agent with.
App Dev2026-06-19
I Started the Ad SDK Before Asking for ATT — the Init-Order Bug That Quietly Lowered First-Session eCPM
When I rolled AdMob mediation out to four iOS apps, only the very first session showed weaker ad revenue. The cause was the order between the ATT prompt and MobileAds initialization. Here is why the order matters, plus how I had Antigravity audit the init sequence across all four apps.
Integrations2026-06-26
Designing MCP Tool Output So It Doesn't Flood Your Agent's Context
When a custom MCP server returns large results in one shot, your Antigravity agent quietly degrades. Field projection, pagination, resource_link, and an output budget keep context from overflowing — shown with concrete TypeScript and measured numbers.
📚RECOMMENDED BOOKS
Build a Large Language Model (From Scratch)
Sebastian Raschka
LLM Dev
Prompt Engineering for LLMs
Berryman & Ziegler
Prompting
AI Engineering
Chip Huyen
AI Eng
* Contains affiliate links
See all →