RORK LABJP
FUNDING — Rork raises $15M, drawing fresh attention to its mobile-first no-code AI positioningMAX-NATIVE — Rork Max reaches native territory React Native can't: AR/LiDAR, Metal 3D, widgets, Dynamic Island, Live Activities, HealthKit, and on-device Core MLMOBILE-FIRST — While Bolt and Lovable focus on web apps, Rork builds mobile apps — production-ready from a plain-language descriptionWWDC — WWDC26 wraps with AI becoming a core OS capability; the iOS 27 generation raises the value of widgets and Live ActivitiesPRICING — Free to start, paid plans from $25/mo, Rork Max at $200/mo — ship fast on Expo, then go native with Max where it pays offALL-APPLE — Rork Max generates pure Swift covering iPhone, iPad, Apple Watch, Apple TV, Vision Pro, and iMessageFUNDING — Rork raises $15M, drawing fresh attention to its mobile-first no-code AI positioningMAX-NATIVE — Rork Max reaches native territory React Native can't: AR/LiDAR, Metal 3D, widgets, Dynamic Island, Live Activities, HealthKit, and on-device Core MLMOBILE-FIRST — While Bolt and Lovable focus on web apps, Rork builds mobile apps — production-ready from a plain-language descriptionWWDC — WWDC26 wraps with AI becoming a core OS capability; the iOS 27 generation raises the value of widgets and Live ActivitiesPRICING — Free to start, paid plans from $25/mo, Rork Max at $200/mo — ship fast on Expo, then go native with Max where it pays offALL-APPLE — Rork Max generates pure Swift covering iPhone, iPad, Apple Watch, Apple TV, Vision Pro, and iMessage
Articles/Business
Business/2026-06-13Advanced

Getting to the Real Revenue Number — A Pipeline that Reconciles AdMob, App Store, Google Play, and Stripe

Dashboard revenue and the money that actually lands in your account do not match. Here is an aggregation pipeline that absorbs currency, timezone, and the gap between estimated and finalized figures across four revenue sources — with the implementation and operating judgment from running six apps.

revenue opsAdMob61App Store Connect7Stripe16data design

Premium Article

One month AdMob's dashboard showed a 12% gain over the prior month, yet the actual payout was flat. I suspected a bug at first, but the cause was simple: the dashboard's "estimated revenue" and the finalized payout are different things. The moment FX settles, post-hoc invalid-traffic adjustments, reaching the payment threshold — several factors make the number on screen diverge from the number in the bank.

Running six apps as an indie developer, revenue arrives from four sources: AdMob, App Store, Google Play, and Stripe. Each has its own currency, its own timezone, its own notion of "estimated versus finalized." Add them up naively and the numbers stop matching somewhere every month. Here is the aggregation pipeline I built to reach the real number by reconciling all four — with the normalization schema, the daily aggregation, and the monthly balancing routine.

Why the numbers diverge — the character of each source

Before designing reconciliation, I had to understand how each of the four sources lies.

AdMob's screen shows estimated revenue. Invalid-traffic deductions and FX settlement pull the month-end finalized figure down by a few percent. In my own records, the gap between estimate and finalized stayed roughly in the 2–6% range. App Store Connect and Google Play have three layers that each differ: the on-screen "sales," the finalized figure in financial reports, and the actual payout net of fees. Stripe is comparatively honest, but refunds and chargebacks arrive later as negatives, so you have to keep occurrence date and settlement date separate or things stop matching across month boundaries.

In other words, no source tells you unambiguously "how much you earned right now." I decided from the start that the foundation of the design is keeping estimated and finalized as separate columns.

A normalization schema — reshape everything into one form

The four data formats are all different, so I normalize them into a common shape first. The schema I use is this.

CREATE TABLE revenue_events (
  app_id        TEXT NOT NULL,        -- one of the six
  source        TEXT NOT NULL,        -- admob / appstore / googleplay / stripe
  kind          TEXT NOT NULL,        -- ad / iap / subscription / refund
  occurred_on   DATE NOT NULL,        -- occurrence date (unified to UTC)
  amount_minor  INTEGER NOT NULL,     -- minor currency units (1 = 1 yen, or 1 cent)
  currency      TEXT NOT NULL,        -- JPY / USD etc.
  status        TEXT NOT NULL,        -- estimated / finalized
  fx_to_jpy     REAL,                 -- FX rate at finalization (NULL allowed for estimated)
  PRIMARY KEY (app_id, source, kind, occurred_on, status)
);

Three things matter. Storing amounts as integers in minor currency units (amount_minor) instead of floats prevents rounding error from accumulating. The status field cleanly separates estimated from finalized, and the primary key prevents duplicate rows for the same day and source. And the FX rate is saved as "the rate at finalization" in fx_to_jpy, not "the rate at aggregation time," so past numbers do not shift when you re-aggregate later.

If you convert currency at the aggregation-time rate by accident, the number you saw at the start of the month differs from the one at the end, and the dashboard becomes untrustworthy. I learned that the hard way once.

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
Why estimates and finalized payouts diverge, broken down per source, and how to reconcile them
A normalization schema that absorbs currency, timezone, and refunds, plus a daily aggregation skeleton
A monthly routine that balances payouts against the books instead of trusting the dashboard at face value
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

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.

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

Related Articles

Business2026-03-25
The Complete Design for Automated Revenue Pipelines in Rork Apps— 5 Engines That Earn While You Sleep
A comprehensive guide to fully automating revenue for Rork-built apps through 5 pipeline engines: subscription retention, ad optimization, dynamic pricing, automated support, and review-driven improvement loops.
Business2026-06-04
Expanding AdMob bidding demand without adding SDKs — what applying to 11 server-side partners taught me about 'enabled ≠ serving'
A working log of actually adding SDK-free, server-side bidding partners to AdMob's bidding sources: the difference between doc-type and form-type sign-up flows, how reCAPTCHA behaves, and the trap that 'partnership enabled' does not mean 'eligible to serve', told from a solo developer's point of view.
Business2026-05-30
Taking Inventory of Your AdMob Ad Units — All the Way to App Open Cooldown Design
When rewarded and interstitial units pile up and you can no longer tell which one fires on which screen, here is the inventory process I use, plus the design for adding a cooldown to an App Open ad that otherwise shows on every launch.
📚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 →