Protect a casual-game economy by verifying store transactions on your backend and recording paid entitlements there before granting coins, hints, or remove-ads access. Use Keychain or Keystore-backed storage for credentials, and cryptographically secure randomness for security tokens. An on-device success callback alone proves nothing to your backend; it is a request to verify a purchase.
Key takeaways
- Separate purchase evidence, account ownership, and entitlement delivery.
- Make grants idempotent: retries must never multiply paid currency.
- Protect secrets locally without treating encrypted saves as authoritative balances.
- Keep reproducible puzzle seeds separate from unguessable security tokens.
- Use obfuscation and anti-tamper controls where they protect an identified client-side asset.
Why is purchase success on-device insufficient?
A cheerful purchase animation belongs in the interface, not in the trust model. For a hypothetical merge game, a modified client could request coins without completing checkout. Our recommendation is to treat every client assertion about paid value as untrusted until the backend verifies its evidence.
Laurens Dauchy describes the boundary in Why In-App Purchases Need Server-Side Validation:
“Your server does not trust it as an assertion; it treats it as a lookup key.”
— Laurens Dauchy, Founder, PTKD Journal
PTKD sells pre-submission scanning, so we treat its articles as informed secondary commentary. Platform documentation takes precedence. In particular, Apple documents local and server receipt validation: local cryptographic verification is legitimate. The limitation is that a modified app can bypass its own enforcement, not that an Apple signature has no value.
For a game shaped like Juice Fruit, we would separate harmless local presentation from paid inventory. This is an architectural recommendation, not a claim about that game's purchase system. Start by naming what an attacker could gain and which component can refuse the request.
How should server-side IAP validation work?
Use current store evidence
Apple's receipt guidance marks verifyReceipt deprecated. For a new integration, use the App Store Server API to obtain Apple-signed transaction information, or verify signed transaction data on your server. Do not mistake decoding a signed payload for verifying its signature.
On Android, send the purchase token to your backend. Google's billing security guidance directs developers to verify purchases through the Google Play Developer API, grant only when the state is PURCHASED, and acknowledge or consume after granting entitlement. A pending payment must remain pending in your game.
Design the grant as a durable workflow
Our rule of thumb is to make delivery recoverable before making it fast. We would implement the following sequence:
- Authenticate the player. Bind the request to a server-recognized account. Treat a client-supplied account identifier as input, not permission.
- Verify the purchase. Check store evidence, expected application, product, environment, and account association where available. Keep store API credentials exclusively on the backend.
- Resolve the reward server-side. Map the verified product to your own catalog. Never accept a client-provided coin amount as the purchased reward.
- Commit atomically. Insert a unique purchase record and its grant in the same database transaction. A duplicate request should return the existing result, including when requests arrive concurrently.
- Complete store processing. Track acknowledgment or consumption as retryable work after durable delivery. Follow the relevant platform lifecycle rather than assuming a successful UI callback completes it.
- Reconcile later changes. Process verified store updates and recheck unresolved purchases. Record reversals rather than erasing the original grant history.
Keep enough purchase history for support to distinguish charged-but-undelivered from already-delivered. In our proposed design, a timeout shows a recoverable checking state and a retry action. It should not encourage another checkout while delivery is uncertain.
Model coins and remove-ads differently
We would model a coin pack as a ledger credit and hint spending as a debit. Replaying purchase evidence must return the original credit, not mint a fresh balance. Remove-ads fits a durable entitlement instead; restoring access should not create consumable currency.
Define refund behavior before release, including what happens when purchased coins have already been spent. We recommend a documented adjustment policy and a support route instead of an unexplained punishment. For offline play, explicitly decide what cached access remains available and what operations must wait for reconciliation.
What belongs in Keychain versus Keystore?
Dauchy's Android Keystore vs iOS Keychain: Design Differences summarizes the distinction:
“The distinction is extraction: Keystore is built around keys you use without seeing, while keychain is built around items you retrieve.”
— Laurens Dauchy, Founder, PTKD Journal
- Apple Keychain: Keychain services stores small secrets in an encrypted database. We would keep an account credential here, not the entire puzzle save.
- Android Keystore: Android documents non-exportable cryptographic keys. Use a key to encrypt a credential, then store its ciphertext separately. Hardware protection depends on device and key capabilities.
- Shared game code: define credential operations with explicit failure handling. Do not promise that native secure storage and a browser save file offer identical protection.
For a Capacitor wrapper, inspect the native implementation behind any storage plugin before selecting it. We would avoid returning long-lived credentials to JavaScript unless required, exclude them from diagnostic logs, and keep entitlement decisions on the backend. Our Capacitor publishing guide provides related packaging context.
Choose Keychain accessibility deliberately. Apple's accessibility documentation says device-only items can be restored to their originating device but do not migrate to another device. That is more precise than PTKD's broad description of excluding them from backups. Plan reauthentication after migration, and avoid a biometric prompt on every casual-game action unless the threat warrants it.
Does encrypting the local database prevent cheating?
In Encrypting local databases in mobile apps: when it matters, Dauchy offers a useful starting point:
“The cheapest encryption is not storing the data.”
— Laurens Dauchy, Founder, PTKD Journal
OWASP's storage requirements cover protecting sensitive stored data and preventing leakage, including through backups and logs. They do not prescribe encrypting every harmless puzzle preference. We would inventory the save schema first, remove unnecessary account data, then choose database or field encryption for the sensitive remainder.
Keep encryption keys separate from the encrypted file. Include migration, key loss, logout, backup, and temporary files in the design review. An encrypted database should not become the only surviving copy of paid inventory; loss of a device credential should trigger account recovery, not loss of purchase history.
Encryption also does not establish server authority. Android explicitly notes that a compromised process may still use its Keystore keys even without extracting them. Our recommendation is therefore to keep local saves as progress caches and reconcile valuable changes server-side. See our web save-system guide for storage architecture context.
When does puzzle randomness become a security issue?
Insecure Random Numbers in Mobile Apps gives a concise test:
“If a value protects something, it comes from the cryptographic generator.”
— Laurens Dauchy, Founder, PTKD Journal
A repeatable daily-puzzle seed is a design tool, not automatically a secret. We would allow deterministic board generation when players should share a challenge. If completing that board awards valuable currency, the backend should validate the claim and prevent duplicate redemption; hiding the seed does not replace those rules.
Authentication tokens and redemption secrets are different. Android recommends SecureRandom for security-sensitive uses, while Apple provides SecRandomCopyBytes. For browser-side random bytes, Web Crypto defines getRandomValues. Use the backend's cryptographic generator for server-issued secrets; never derive them from a daily seed, timestamp, or player name.
We would also bind redemption tokens to their purpose and account, expire them, and enforce single use server-side. Randomness makes a token hard to guess; authorization decides whether its holder may perform the action. Keep cosmetic randomness and security randomness separate in code review.
Where do anti-tamper checks and R8 help?
In Debugger Detection and Anti-Tamper: When It Is Worth It, Dauchy identifies the constraint:
“Every one of these runs inside the process being attacked, which is the fundamental problem and not an implementation weakness.”
— Laurens Dauchy, Founder, PTKD Journal
OWASP frames resilience controls as threat-specific defense in depth. We would prioritize them where the client enforces value, such as offline premium access or packaged content. They cannot repair a backend that accepts fabricated rewards. Treat detection as a risk signal with a recovery path for legitimate players.
Android Code Obfuscation with R8: What It Protects makes the narrower point:
“A renamed APK decompiles.”
— Laurens Dauchy, Founder, PTKD Journal
PTKD also says R8 is on by default for release builds. Do not rely on that generalization: Android's optimization guide explicitly describes enabling optimization in release configuration. Follow the primary documentation for your installed tooling. We would inspect the actual wrapper build and test billing and storage plugins in the optimized artifact.
What would NabiGame Studio check before shipping?
Our recommendation is to review the smallest complete purchase journey before adding elaborate defenses. For a small studio, clear recovery behavior deserves the same attention as rejection behavior:
- Replay: submit the same evidence repeatedly and concurrently; verify the ledger remains unchanged after delivery.
- Identity: attempt redemption under another account and with the wrong product or environment.
- Interruption: disconnect after payment, restart during delivery, and retry after a backend timeout.
- Lifecycle: exercise pending, canceled, restored, and refunded purchases, including refunds after spending.
- Storage: inspect logs and backups, then test missing keys, migration, logout, and offline recovery.
- Release: test the signed, optimized build with real plugin paths, not only a development preview.
These are proposed acceptance checks, not reported NabiGame results. Our game development services provide context for collaboration; if a purchase flow needs an independent review, tell us about the game and its platform targets.
FAQ
Does every offline puzzle game need a server?
We would decide based on the value at risk and the desired recovery experience. Local validation can suit a deliberately offline design, but we recommend server authority for shared balances and account-linked paid inventory.
Can a secure-storage plugin hold the coin balance?
It can hold a cached representation, but we would not trust that representation as proof of ownership. Keep the authoritative ledger on the backend and define offline spending limits explicitly.
Should daily-puzzle seeds use cryptographic randomness?
Use deterministic seeds when reproducibility is the goal and predictability causes no security harm. Keep authentication and redemption secrets on a separate cryptographic path.
Does R8 protect secrets embedded in a wrapper?
Do not design around that assumption. Our recommendation is to keep privileged credentials on the server and use obfuscation only as an additional obstacle to inspection.
What happens when verification is unavailable?
We would preserve the pending purchase evidence and offer a retry without granting new unverified currency. Define cached access separately so a temporary outage does not unnecessarily interrupt already-authorized play.
Sources
- PTKD: Why In-App Purchases Need Server-Side Validation
- Apple: Choosing a receipt validation technique
- Apple: Validating receipts with the App Store
- Google: Fight fraud and abuse
- PTKD: Android Keystore vs iOS Keychain: Design Differences
- Apple: Keychain services
- Android: Keystore system
- Apple: Restricting keychain item accessibility
- PTKD: Encrypting local databases in mobile apps: when it matters
- OWASP: MASVS-STORAGE
- PTKD: Insecure Random Numbers in Mobile Apps
- Android: Weak PRNG
- Apple: SecRandomCopyBytes
- W3C: Web Crypto getRandomValues
- PTKD: Debugger Detection and Anti-Tamper: When It Is Worth It
- OWASP: MASVS-RESILIENCE
- PTKD: Android Code Obfuscation with R8: What It Protects
- Android: Enable app optimization with R8

Comments
Loading comments…