Reward app with 5-network ad mediation

Earning Point is the project where I first got paid real money for software. That sentence carries more weight than it probably looks — not because of the amount, but because of what it required me to become. Until then, every project I had built was ultimately for myself: a portfolio item, a practice run, something that only failed or succeeded in my own estimation. This one had a client, a deadline, a signed understanding, and a non-technical buyer on the other side who was going to hand someone else a deliverable and stake his own credibility on it working.
The app itself is an Android reward platform built for India's tier-2 and tier-3 internet market. Users watch YouTube videos, complete offerwall surveys through CPX Research, spin a lucky wheel, claim daily bonuses, and refer friends — all accumulating points that redeem against an admin-controlled payout queue in cash or vouchers. The monetisation logic is clear: ad impressions and survey completions fund the payout pool, and the platform is the clearinghouse. I had seen this model as a user. Building it as a developer, with someone else's money and reputation downstream of my decisions, was a qualitatively different situation.
The full deliverable was version 5.9: 318 Java source files, a complete Laravel admin panel, 27 MariaDB tables, and five ad networks running through a single mediation layer. It shipped as a turnkey package. The client needed zero technical capability to operate it.
My client did not know what a git repository was. That is context, not a criticism. It defined the contract: I was not delivering a codebase, I was delivering a product — something a non-developer could hand to a hosting provider and have running within an afternoon. That constraint shaped every decision I made about packaging, configuration management, and documentation from the first week.
The harder problem was underneath the product surface. Any reward platform that pays out real money has an obvious structural exploit: automate the point-credit API calls and drain the payout pool without engaging with any content. On a casual internal tool this is manageable. On a consumer app where points translate to real ₹ withdrawals — advertised to users who are actively looking for ways to earn — a farming script that got loose could empty the reward fund in hours. I had read enough forum threads about Indian reward platforms being systematically drained to take this as a genuine threat, not a theoretical edge case. The business model only survives if the point-earning is real.
India also presents an advertising fill-rate problem. AdMob alone in a tier-3 market delivers poor eCPM — sometimes failing to fill at all during off-peak hours. Ad revenue directly funds the payout pool on this platform. A fill-rate gap is not just a revenue event; it is a user trust event, because users who complete tasks and receive no reward attribution have no way to distinguish a fill failure from a fraud. This required a mediation strategy, not a single network.
The Android app targets SDK 34 with a minSdk of 21. Architecture is Activity-based — the decision to stay off Jetpack Compose was deliberate, because the client's operator network included developers who would need to maintain this after me, and Java Activity patterns have a much wider support base in India's freelance Android market than Compose does. Retrofit 2 with OkHttp 3 handles all networking. An ApiClient.java singleton and ApiInterface.java define every endpoint contract in one place.
The API_URL and API_KEY are injected from gradle.properties via buildConfigField at build time — they never appear in source. White-labelling or repointing the app at a new server is a two-line change in one file, then a rebuild. For a client who may move hosting providers without informing me in advance, that architecture decision has real operational value. Auth tokens from Laravel Sanctum live in SharedPreferences via a Session.java helper. Push notifications run through Firebase Cloud Messaging.
The lucky wheel is a separate luckyWheel local Gradle subproject bundled into the main app. I broke it out because a third-party library would have imposed its own data model — sector counts, animation timing, prize weights — and I needed all of that to stay perfectly synchronised with whatever the wheel_points admin table was configured to at any moment. Owning the module meant owning that contract.
The Laravel panel runs PHP 8.1 with Eloquent ORM against a 27-table MariaDB schema. Yajra Datatables handles server-side pagination on transaction logs that can grow to millions of rows. Spatie Laravel Permission manages sub-admin RBAC so the client can delegate panel access without granting root. The CPX Research postback endpoint receives survey completions, verifies the shared secret signature, and atomically credits both the offerwall_earing and transaction tables in a single database transaction — partial credits are not possible.
AppLovin mediation over a manual network waterfall. Five ad networks — AppLovin, AdMob, Facebook Audience Network, Unity Ads, and IronSource — feed into a single SDK call. The mediation layer handles waterfall ordering, per-network timeout, and fill-rate optimisation automatically. The alternative was maintaining brittle manual fallback logic across five integrations, where a single SDK update from any one network could silently break the chain. AppLovin's mediation is the standard approach for Indian consumer apps that need to extract real eCPM from a tier-3 market. It was the correct default and I used it without hesitation.
Firebase App Check with Play Integrity attestation. This is the farming answer. Play Integrity attestation requires a token generated by a real Android device with a verified Play Store signature — emulators fail, sideloaded builds fail, scripted HTTP clients fail, all before the request reaches my backend rate limiting. The setup surface is real: SHA certificate registration in Play Console, Firebase project linking, enforcement mode toggling per environment. I went through all of it because the alternative was a payout pool with no perimeter. The farming problem is not exotic — it is the default outcome for any reward platform that skips attestation. Skipping it would have been negligent, not pragmatic.
BuildConfig injection for API credentials. Hardcoding the backend URL in source is the most common mistake I see in Indian Android freelance projects. It means any configuration change requires source access, a recompile, and a new APK build. buildConfigField in gradle.properties makes that two lines. For a white-label client this distinction matters every single time the hosting situation changes.
luckyWheel as a local Gradle module. The wheel's prize distribution, animation curve, and sector weights are business logic, not UI decoration. Owning the module kept that logic entirely under my control and the client's admin panel in sync.
Version 5.9 shipped as a ZIP archive: full source tree, a pre-seeded MariaDB SQL dump with all admin configuration tables populated, the production signing keystore, a GitBook documentation site I wrote specifically assuming the reader had never opened Android Studio, and a How to Update.txt that walked through the four steps from APK build to Play Console upload. The client needed zero technical hand-holding after handoff. That is what I mean by a real delivery versus a GitHub link.
The technical decisions were all defensible. What the project actually taught me was the shape of a complete delivery — the keystore, the SQL dump, the documentation that anticipates every question a non-developer will ask at 11pm when they're trying to redeploy. Every freelance project I have shipped since has used this package as the template. The first real deadline teaches you things that no practice project can replicate, because the cost of getting it wrong is someone else's.
Java source files
MariaDB tables
ad networks mediated
delivered version
Forensic stabilisation and feature expansion of a production streaming platform across web, mobile, and TV
Offline-first Flutter app with GetX state management, Appwrite backend, and Lottie animations
Revenue-share HTML5 game hub embedded into the Prachyam OTT platform via Flutter WebView and React
Did this resonate?