●DEADLINE — Four days remain until Google Play requires Android 16 (API level 36). From August 31 it applies to new apps and to updates of existing ones alike●RULES — The submission rule and the visibility rule are separate. An app you have stopped updating still disappears for new users on newer devices if it targets below API 35●EXTENSION — An extension keeps you shipping to all users until November 1, but the form lives in Play Console and has to be filed before the deadline passes●EXPO — Expo SDK 57 moves React Native from 0.85 to 0.86 while React stays at 19.2, and 0.86 is intended to land without breaking changes●HERMES — 57.0.9 updates React Native to 0.86.2 and clears the Hermes V1 memory regression from SDK 56, which shows up in apps importing reanimated or worklets●PREBUILD — expo prebuild now clears and regenerates the native android and ios directories by default, so hand-edited native changes vanish unless you audit for them first●DEADLINE — Four days remain until Google Play requires Android 16 (API level 36). From August 31 it applies to new apps and to updates of existing ones alike●RULES — The submission rule and the visibility rule are separate. An app you have stopped updating still disappears for new users on newer devices if it targets below API 35●EXTENSION — An extension keeps you shipping to all users until November 1, but the form lives in Play Console and has to be filed before the deadline passes●EXPO — Expo SDK 57 moves React Native from 0.85 to 0.86 while React stays at 19.2, and 0.86 is intended to land without breaking changes●HERMES — 57.0.9 updates React Native to 0.86.2 and clears the Hermes V1 memory regression from SDK 56, which shows up in apps importing reanimated or worklets●PREBUILD — expo prebuild now clears and regenerates the native android and ios directories by default, so hand-edited native changes vanish unless you audit for them first
Putting AdMob Bidding into Production for a Rork App — Five Networks Bidding in Parallel, eCPM Trends, and Daily Operations
I moved the AdMob mediation layer of my Rork-generated apps from waterfall to bidding, with five ad networks bidding in parallel. Here are my real-world numbers after three weeks of production, the SDK pitfalls, and how I delegate daily monitoring to Claude in Chrome.
The morning I opened the AdMob dashboard and saw the bidding acceptance rate sitting near 100% across the day, I finally felt that the migration work was behind me. The Rork-generated apps I run had been moving from waterfall mediation to bidding for the past three weeks, and that day was the first one where the numbers looked stable.
I have been shipping mobile apps as an indie developer since 2014. Among everything that has shifted around AdMob, the move from waterfall (call networks in sequence) to bidding (have them bid in parallel) is one of the most meaningful changes for solo operators. On the surface it looks like a single checkbox in the AdMob console, but actually running it in production with several networks side by side took quite a bit of tuning across SDK setup, console configuration, and daily monitoring.
The migration covered six wallpaper apps. I had planned to move them one at a time, but a round of SDK updates landed across the whole catalog at once, and all six ended up going through the switch in parallel over three weeks. Running the same setup six times in a row turned out to be the useful part: it made very clear which steps snag every single time and which ones only ever bit me once.
What follows are the notes from that run. Starting from the Rork-generated baseline, I'll walk through what it takes to have AdMob plus five networks (AppLovin / Meta Audience Network / Unity Ads / Pangle / Liftoff) bid in parallel in production — the integration changes, the pitfalls that cost me the most time, the eCPM and fill rate shifts against the prior waterfall setup, and the daily monitoring loop I now run through Claude in Chrome.
Why I Moved off Waterfall — The Ceiling I Could Feel but Not Measure
With waterfall, AdMob queries networks in order of their declared eCPM floor. If I set AppLovin's floor at $2.00 and Unity Ads at $1.50, requests go to AppLovin first and fall through to Unity Ads only when AppLovin doesn't fill. It is a clean mental model, but in my apps it had two limits I kept bumping into.
The first one was that floor values are static, which means they cannot absorb variability across currency, hour of day, and audience composition. AppLovin tends to dominate during Japanese hours; Meta Audience Network shows up stronger during overseas hours. Watching the dashboards I could see the patterns, but adjusting floor values by hand to chase them is not realistic when you have six apps to maintain.
The second one was that unfilled-impression opportunity cost is hard to see. In the waterfall logs you can see "AppLovin didn't fill, so the request went to network 2," but you can't see "if all five networks had bid in parallel, what eCPM would the auction have produced?" Bidding shows you that number every time, because the auction actually happens.
In a bidding setup, AdMob sends a parallel bid request to all participating networks and instantly picks the highest bid that arrives inside the time window. The mechanism removes both limits above. Google has been steering publishers toward bidding in their docs as well, and the case for it is especially strong when you have multiple SDKs co-resident in the same app.
The final shape I landed on is a hybrid: one or two networks stay on the waterfall (because their adapter doesn't support bidding yet), and the five networks that do support bidding compete in parallel.
Separating the Code You Leave Alone from the Code You Touch
A Rork-generated app comes with a reasonable AdMob initialization scaffold. In my wallpaper apps I keep that initialization centralized in App.tsx, and I let all the mediation SDKs load as AdMob adapters rather than as direct SDKs. This keeps the code footprint small and matches what bidding wants.
I use Google's official react-native-google-mobile-ads package rather than the older Firebase wrapper. Bidding has strict requirements around SDK and adapter versions — get them wrong, and bidding will appear to be enabled in the console but never actually fill an impression.
Adapters are picked up through EAS Build natively. In app.json I declare them like this:
{ "plugins": [ [ "react-native-google-mobile-ads", { "androidAppId": "ca-app-pub-XXXXXXXXXXXXXXXX~XXXXXXXXXX", "iosAppId": "ca-app-pub-XXXXXXXXXXXXXXXX~XXXXXXXXXX", "userTrackingUsageDescription": "Used to measure ad performance for the apps you enjoy.", "skAdNetworkItems": [ "cstr6suwn9.skadnetwork", "4fzdc2evr5.skadnetwork", "ydx93a7ass.skadnetwork" ] } ] ]}
Every network publishes the SKAdNetwork IDs it needs declared in Info.plist. AppLovin, Unity Ads, Pangle, Liftoff, and Meta Audience Network each have their own list, and I learned to enumerate all of them in skAdNetworkItems. Miss one, and on iOS you can see impressions in the console but the attribution silently breaks, which means the eCPM reports do not reflect actual revenue several days later.
The Rork-generated code itself I leave essentially intact. The three additions I make are: an initialization hook, environment-variable-based ad unit IDs, and an adjustment to where the App Tracking Transparency (ATT) prompt sits in the launch sequence.
The detail that matters is resolving the ATT dialog before AdMob initializes. Reversing the order causes personalized ads to drop off on iOS 14.5+ devices and, in my measurements, takes 20–25% off the bidding eCPM. The same principle applied under waterfall, but the impact is larger now that bidding amplifies competition for ATT-authorized impressions.
✦
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
✦Concrete SDK integration steps for AdMob Bidding alongside an existing waterfall setup, with the gotchas I hit on iOS and Android
✦Three weeks of measured eCPM, fill rate, and latency numbers across five networks (AppLovin / Meta Audience Network / Unity Ads / Pangle / Liftoff), plus how the auction wins actually split between them
✦A diagnostic order for telling apart a bid that never arrived, a bid that lost, and a win whose creative failed to load — plus the Claude in Chrome prompt I use for the morning rounds
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.
Configuring Bidding in the AdMob Console — The Pitfalls That Cost Me Days
This is where most of the practical learning happened. Below are the three pitfalls that absorbed the most time during my migration.
Pitfall 1: Enabling Bidding in AdMob Is Not Enough
In the AdMob console you create a new Mediation Group, pick the bidding-capable networks, and save. The screen confirms "Bidding enabled." But until you complete the mapping inside each network's own management console — minting a bidding-specific ID and pasting it back into AdMob — no actual bid requests reach those networks.
I lost two days on AppLovin, three days on Meta Audience Network, and a day on Pangle this way. Each network's flow is slightly different, and each one has a setting that looks connected but isn't until you paste the right bidding ID into AdMob.
Pitfall 2: Adapter Versions Are Strict
Bidding support is gated by adapter major version. AppLovin SDK 11.x is waterfall-only; 12.x is where bidding starts. Each network has its own breakpoint. I grep Podfile.lock and android/app/build.gradle to confirm versions before flipping bidding on.
Three of my five adapters needed updates as part of the migration. Adapter updates sometimes change how ad unit IDs are passed in at initialization time, so I treated each one as a small refactor rather than a version bump.
If even one adapter is in a state other than READY, that network drops out of the auction completely. I now make a habit of checking the Logcat output at startup before shipping any release.
Pitfall 3: Test Devices Cannot Reproduce Bidding
The AdMob test ad unit IDs never reflect bidding behavior — they always return test creatives. To validate bidding you have to use production ad units, but you cannot let your own developer device serve real impressions in production without risking ad spam.
My workflow is to enroll a handful of beta testers through TestFlight and Google Play Internal Testing, then watch the "Bidding report" section in the AdMob console for each ad unit. When I see "impressions through bidding > 0," I consider the migration successful for that app.
Three Weeks of Production Numbers — eCPM, Fill Rate, and Latency
Across five wallpaper apps that I migrated in waves over three weeks, the weighted-average comparison against the prior waterfall configuration looks like this. Numbers move app by app, but the direction has been consistent.
Metric
Waterfall
After bidding
Change
Banner eCPM (Japan)
$0.42
$0.51
+21%
Interstitial eCPM (Japan)
$4.20
$5.45
+29%
Rewarded eCPM (Japan)
$7.80
$9.10
+16%
Banner fill rate
92.4%
96.8%
+4.4pt
Interstitial fill rate
88.6%
94.1%
+5.5pt
Average ad load latency
712ms
540ms
-24%
The biggest mover was interstitial eCPM. During hours where my Japanese audience is concentrated, interstitial eCPM peaked at +35% above the waterfall baseline. The story behind that number is that Meta Audience Network surprisingly often submits very strong bids in Japan, and under waterfall it was sitting behind AppLovin and rarely getting served. With bidding it competes head-on every impression.
Fill rate improvements sound modest as percentages, but five percentage points of interstitial fill across millions of impressions per month adds up to real money. Latency dropping was the result I least expected. I assumed parallel requests would be slower than sequential ones, but the waterfall path's tail latency was dragged down by "network 1 timed out, try network 2," which the bidding model bounds with a single auction window.
It was also worth looking at how the wins actually split. Averaged across three weeks of interstitial traffic, the share of auctions each network won came out roughly like this.
Ad network
Approximate win share
Where it was strongest
AppLovin
Just over 30%
Steady across all hours — the floor of the whole mix
Meta Audience Network
Just under 30%
Japanese evening hours, where it posts the top price on most days
Unity Ads
Under 20%
Spikes when game advertisers are running campaigns
Pangle
Around 10%
Asia broadly; thin inventory specifically in Japan
Liftoff
A few percent
Overnight hours, when everyone else pulls back
The row that mattered most to me was the last one, not the first. A few percent looks like something you could drop, but the hours Liftoff wins are clustered where the other four go quiet. A network can be small in aggregate and still be the thing keeping your fill rate from falling through the floor at 3 a.m. Not judging a network by its share alone is a habit I picked up somewhere around the fourth migration.
Separating "No Bid" from "No Fill" — Not Arriving vs. Not Winning
Bidding changed how I debug. Under waterfall there were two states: a network filled, or it didn't. Under bidding, three separate things sit side by side. The bid never arrives. The bid arrives but loses on price. The bid wins and then the creative fails to load. Confuse them and you will spend your afternoon fixing the wrong layer.
What makes it harder is that the vocabulary doesn't line up across dashboards — what one network calls "no bid" another labels "unfilled." Watching six apps at once, I gave up on reading each dashboard on its own terms and folded them into a single table first.
Symptom
How it looks in AdMob
What is actually happening
Where to fix it
Bid requests go out, bid responses stay at zero
Bid rate of 0% for that network in Bidding analysis
Mapping incomplete, or the wrong bidding ID was pasted in
The network's own console
Bid responses arrive but almost never win
eCPM shows up, revenue contribution is near zero
Normal. It is simply being outbid
Leave it alone
Wins the auction but impressions don't follow
Gap between match rate and show rate
The adapter is failing to load the creative
App-side SDK / adapter version
Bid rate is zero in one country only
Invisible unless you split by country
That country is out of the network's serving footprint, or inventory is negligible
Keep that country on waterfall
I labeled the second row "normal" for a reason. On day one of the migration, Liftoff's win share sat around 3% and I spent half a day bouncing between consoles convinced I had misconfigured something. Bid responses were in fact coming back roughly nine times out of ten — Liftoff was just bidding less than the other four. Participating and winning are different questions under bidding, and the fix was to always read bid rate and win rate as two separate numbers rather than one health indicator.
The fourth row is the one aggregate dashboards will never show you. Pangle's overall bid rate looked perfectly healthy in my account while Japan specifically sat at nearly zero for over a week. Inventory from other countries kept the average respectable, so nothing ever crossed an alert threshold. For the first week after a migration, it is worth building a country-split view for your top three or four markets and reading that instead of the summary.
In practice, working through it in this order got me to the answer fastest:
Check bid rate. Zero means the network's console, not your app. No amount of client-side work will move it.
Check win rate. Low on its own is normal — that's a price and inventory story, not a defect.
Compare match rate against show rate. A gap points at the adapter, which sends you back to the initialization logs.
Split by country. When the first three look fine but revenue still isn't moving, the reason is usually hiding here.
Estimated Revenue Will Not Match the Networks — Decide the Tolerance First
The first month-end after the migration is where I got stuck. AdMob's estimated revenue never matched the networks' own figures under waterfall either, but with five networks competing for the same impression, there are simply more places for the gap to come from. The gap itself isn't a defect — but treating every gap as an incident will eat your entire month-end.
So I drew the line in advance, separating discrepancies worth investigating from discrepancies that are just how the system works. This is the reference I use across the six apps.
Source of the gap
Which direction it runs
How I handle it
Reporting time zones (AdMob on Pacific time, networks on UTC or local)
Large daily, essentially gone monthly
Never reconcile daily. Monthly only
Invalid traffic deducted after the fact
AdMob revises downward later
Finalizes in the first days of the next month. Swap in the confirmed figure then
Exchange rate application date
A few percent when viewed in local currency
Reconcile in USD, convert once at the very end
Payment thresholds carrying balances forward
Only the payout is delayed
Track revenue and cash in separate sheets
As long as a gap fits inside those four explanations, I don't investigate. I only reach for the consoles when a monthly reconciliation lands in double-digit percentages. Before I had that rule, small daily discrepancies would catch my eye and stall the morning. What the operation actually needed wasn't a perfect match — it was a threshold for calling something abnormal.
One more trap specific to bidding: the same impression appears twice if you aren't careful. AdMob records one impression for "AppLovin won this auction," and AppLovin records one for serving it. Add the two dashboards together and your total impressions come out close to double the truth. Obvious in hindsight, and yet the first spreadsheet I built to consolidate five networks did exactly that. Under a bidding setup, AdMob is the single source of truth for impressions, and the network consoles are there for eCPM and delivery errors only. Splitting the roles that way is what finally made month-end quiet.
Delegating the Daily Round to Claude in Chrome
The economics are one story; the daily operations are another. With six apps in production, opening every AdMob console and every network's dashboard every morning is not realistic. AdMob has an API, but the network dashboards each require their own API setup, and standing all of that up myself for a six-app catalog is a full project on its own.
I have been doing my morning round through Claude in Chrome instead. What convinced me was an inversion I kept running into: the screens with no API tend to be exactly the ones holding numbers that only matter if you look at them daily. Each network's diagnostics page and its "no bid" breakdown live precisely there.
The prompt I send each morning looks roughly like this:
# Morning AdMob round
Visit each site in order and report the listed metrics in a single markdown table.
Add a "Notes" column for anything unusual.
1. AdMob Console (https://apps.admob.com)
- Past 24h estimated earnings (per app)
- Past 24h eCPM (banner / interstitial / rewarded, per app)
- Bidding acceptance rate (Mediation > Optimization > Bidding analysis)
2. AppLovin Dashboard
- Past 24h Revenue / Impressions / eCPM (per app)
- Delivery error rate (Diagnostics)
3. Meta Audience Network
- Past 24h Revenue / Impressions / eCPM
- Watch for elevated "no bid" rates
4. Same for Unity Ads / Pangle / Liftoff.
## Alert rules
- Mark any app with eCPM down >25% day-over-day with "⚠️".
- Mark any app with fill rate under 80% with "🔻".
- If delivery error rate exceeds 5%, add a separate root-cause section.
Claude in Chrome opens each site, pulls the tables, and sends me a single consolidated digest. I read the report on my phone while the coffee brews. The state of six apps in production is in my hands before the day starts.
It is not fully autonomous yet. AppLovin sometimes logs me out daily; when that happens, Claude pauses partway through and asks for the 2FA code, which I provide. Even with that small interaction, the time savings versus visiting six dashboards manually are an order of magnitude.
Rollback Design — What I Always Have Ready Before Flipping the Switch
Bidding has clear upside, but it has one operational constraint: configuration lives per-app in the console, and if something goes wrong for a single app you need a way back. I do not flip an app to bidding without the following three pieces of fallback in place.
Preserve the previous mediation group. Instead of deleting the waterfall mediation group when I create the bidding one, I keep it and set its status to disabled. If bidding goes sideways, flipping the status back is one click and zero rebuilds.
Switch ad unit IDs through Firebase Remote Config. The app carries two sets of ad unit IDs — the bidding ones and the legacy ones — and reads which set to use from Remote Config. This means I can roll back an app without resubmitting to the stores.
Tag ad-related exceptions in Crashlytics. Each network's stack traces look different. I attach custom keys to ad-related exceptions so I can filter Crashlytics by network when something starts failing on a specific SDK.
I have actually used the Remote Config switch once during the migration. One app showed unexpected eCPM movement for the first 48 hours and I rolled it back, investigated, fixed an adapter version mismatch, and re-enabled bidding two days later. Without that switch in place I would have been waiting on App Store review to ship a hotfix.
Should an Indie Developer Move to Bidding?
For solo developers considering the move, here are the three questions I would ask first.
The first is whether the mediation SDKs you currently use are on the bidding-capable adapter generation. Networks whose adapters have stopped getting major updates are not migration candidates; leave them in the waterfall residual.
The second is whether your traffic volume justifies the work. I would say that around 5,000–10,000 DAU is the threshold where bidding starts paying back the setup time. Below that, the number of bidders per auction is low enough that the eCPM gains tend not to be dramatic, and the operational overhead does not amortize.
The third is whether you can sustain daily monitoring. Bidding revenue is more sensitive to network health than waterfall revenue, because if one major bidder goes quiet the auction can soften noticeably. If you can wire up something like Claude in Chrome (or a custom API integration) for the daily round, you're fine. If not, a well-tuned waterfall might be the better operational fit even if the absolute eCPM is lower.
In my own apps, after three weeks of work and adjustment, I feel I should have done this migration sooner. Rork-generated apps come with the AdMob foundations already in place, which makes bidding migration easier than it would be on a hand-built codebase, not harder.
If you are still on waterfall, the most practical next step is to pick one app, confirm your SDK adapter versions support bidding, and walk through the console mapping with the three pitfalls above in mind. Once the first app is stable in production, the rest of your portfolio takes far less time per app.
I am still learning as I go. If any of this is useful to other indie developers monetizing through AdMob, I would be glad to hear about your numbers as well. Thanks very much for reading.
Share
Thank You for Reading
Rork 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.