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.

antigravity437admob17monetization31app-dev49security20

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-30
The App Open Ad Antigravity Wrote for Me Fires Every Time I Return From My Own Paywall or Rewarded Video
Ask Antigravity to add an App Open ad and it shows one the instant you return from your own rewarded video or the Google Play purchase sheet — which also brushes against AdMob policy. Here is a foreground arbiter that records why the app came back, with working Kotlin and a verification matrix.
App Dev2026-06-28
Adding Mediation Partners Quietly Starved My iOS Attribution — Reconciling SKAdNetwork IDs Across Four Apps
I added mediation partners but iOS revenue barely moved — the cause was missing SKAdNetwork IDs in Info.plist. Here is how I reconciled SKAdNetworkItems across four apps, using an Antigravity agent as the matcher while keeping the revenue decisions by hand.
📚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 →