ANTIGRAVITY LABJP
Articles/App Development
App Development/2026-06-25Advanced

An Agent Granted 'Watch an Ad to Unlock a Wallpaper' Entirely Client-Side — Re-Verifying Reward Grants with AdMob SSV

I asked an Antigravity agent to wire up 'watch a rewarded ad to unlock a wallpaper,' and it returned an implementation that wrote the unlock flag client-side only. Here is why that is not enough, how I re-verified the reward grant with AdMob server-side verification (SSV), and how I stopped double grants too.

antigravity390admob15monetization30app-dev39security13

Premium Article

Watch one rewarded ad, unlock one paid wallpaper. When I asked an Antigravity agent to wire up that small flow, the code it returned did work. The wallpaper unlocked the moment the ad closed, and it stayed unlocked the next time the app opened. The trouble was what backed that "stayed unlocked": a single boolean written to the device's local preferences.

I run wallpaper and healing apps on Google Play as an indie developer. Instead of charging, I have long used a design where watching a rewarded ad unlocks a few wallpapers. That is exactly why I stopped the moment I saw this code. This is a question about who gets to decide that a reward — the unlock — is legitimate. It is a trust-boundary question.

The agent took the shortest path to "watch an ad to unlock"

What the agent wrote looked roughly like this. In the ad's reward callback, it persisted the unlock flag straight to the device.

// The agent's first proposal (grant client-side only)
rewardedAd.show(activity) { rewardItem ->
    // view complete = persist the unlock immediately, on device
    prefs.edit()
        .putBoolean("wallpaper_${wallpaperId}_unlocked", true)
        .apply()
    unlockUi(wallpaperId)
}

The behavior looks correct. Watch the ad, it unlocks; restart, it persists. Tests pass too. But that one line — putBoolean(... true) — is the only basis for "is this wallpaper unlocked," and in production that is the weak point.

Local preferences live within reach of the user. On a rooted device or a modified build, you can flip that flag to true without ever watching an ad. The ad callback itself, as long as it stays entirely inside the client, is also a candidate for tampering. In other words both the fact ("an ad was watched") and the decision ("it may be unlocked") were sealed inside the client.

Small as it is, this leaks ad revenue directly. A rewarded ad is a contract — a reward in exchange for the value of a view — and leaving a path to take the reward without paying the view undermines it.

Why a client-side grant alone is not enough

This is where AdMob's Server-Side Verification (SSV) earns its place. When you enable SSV on a rewarded ad unit, Google calls a callback URL on your server every time a user earns a reward. That callback carries the fact that Google considers the reward grantable, signed with ECDSA.

The official docs also recommend using the client-side callback for immediate UX while validating reward legitimacy through SSV (Validate server-side verification (SSV) callbacks). They state plainly that the more a reward affects your app economy, the more the verified server-side callback should be treated as the source of truth. My wallpaper unlock was precisely a reward that "affects the economy."

The line to take home is single: measuring "an ad was watched" can be left to the client and the ad SDK. But deciding "this may be unlocked" is made only by a server that can verify the signature. What the agent short-circuited was collapsing those two into one.

AspectClient-side grant onlyGrant verified by SSV
Basis for unlockLocal flag on deviceGoogle-signed callback
Spoof resistanceBypassable by flag edit / APK patchGrants that fail signature check are rejected
Double grantUndetectableIdempotent via transaction_id
Where truth livesThe deviceThe server

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
Understand exactly why a client-side unlock flag can be spoofed, and close the gap today with AdMob rewarded SSV
Drop a complete Cloudflare Worker SSV verifier into your own app, including the DER-signature pitfall that Web Crypto silently rejects
Take home an idempotent grant built on transaction_id to stop replays and double grants, plus a clear line: let the agent wire and measure, keep the trust boundary yourself
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

App Dev2026-03-27
Antigravity × AdMob Revenue Optimization Guide — Streamline Mobile Ad Implementation with AI Agents
Learn how to leverage Antigravity's AI agents to streamline Google AdMob implementation and maximize mobile ad revenue. Covers ad format selection, waterfall optimization, and A/B testing strategies.
App Dev2026-06-21
The Back Button Showed an Interstitial Sometimes, Not Others — Rewriting Nested ifs Into a List of Independent Guards
Interstitial display on back press was unstable because nested if statements hid the priority between conditions. Here is how I split it into reason-returning guards and generated tests from a decision table.
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.
📚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 →