Premium currency is one of the most valuable things in a game. It may represent real money, paid progression, rare items, or player trust. If attackers can change it on the client, your economy can break quickly.
Why the client should not own currency
A Unity client runs on the player's device. That means memory editors, save editors, proxies, and modified builds can all try to change what the client sends or stores. If the client can say "my new balance is 999999" and the backend accepts it, the economy is not protected.
A safer design lets the client request actions. The server decides the result. The client can say, "I want to buy this skin," but the server checks the balance, subtracts the cost, grants the item, and records the transaction.
This transaction record matters as much as the balance. Keep an audit trail for grants, spends, refunds, compensation, and support actions so you can explain why a balance changed. When a player reports missing currency, the ledger is often more useful than the current total alone.
The backend should treat every economy change as a transaction, not as a replacement balance. A spend request should include the item or offer ID, the expected price, the account, and an idempotency key. The server can then check whether that exact action was already processed before it changes the ledger.
Validate purchases before granting currency
Purchase callbacks are useful, but they should not be the only proof. A modified client may fake a success callback. For paid currency, use platform receipt validation with Google Play, Apple, Steam, or your chosen store.
Also watch for duplicate receipts, refunded purchases, replayed requests, and suspicious purchase timing. A simple transaction ID check can prevent the same receipt from being claimed again and again.
Receipt validation should happen on a trusted service when possible. The client can send the receipt or purchase token, but the backend should verify it with the store, bind it to the account, store the transaction ID, and only then grant currency. That flow also gives you one place to handle refunds, chargebacks, and duplicate claims.
Be careful with retries. Mobile networks fail, store callbacks can be delayed, and players may press a button twice. Idempotent purchase processing lets the game retry safely without granting currency more than once for the same store transaction.
Separate earned and paid currency rules
Many games mix paid currency, earned currency, event tokens, and bonus grants. Treat each bucket according to its risk. Paid currency needs the strongest receipt and refund handling. Earned currency needs strong reward validation. Temporary event tokens need expiry and claim limits.
Clear buckets also make support and rollback decisions easier. If a suspicious offline session created earned currency, you can review that bucket without accidentally removing paid currency that came from a valid store receipt.
Separate buckets also let you apply different spending rules. For example, paid currency may be refundable and tied to store receipts, while bonus currency may expire or be spent first. Make those rules explicit on the server so the client cannot choose the most favorable bucket during a modified request.
Protect local values too
Even if the server owns the real balance, the client still shows and caches currency. Protecting those local values helps stop simple memory edits, confusing UI changes, and offline tampering.
Protected memory and protected saves are useful here. They reduce easy scans and help detect when a local value has been changed outside the game flow.
Local protection should cover both the value and the code path that uses it. If the display balance is protected but the purchase button trusts a plain local variable, an attacker still has a useful target. Runtime checks can compare protected display state, last server balance, and pending transaction state before showing high-value actions as available.
Handle offline currency carefully
Offline support is possible, but it needs rules. Limit how much premium currency can be earned or spent offline. Store pending actions. When the player reconnects, the server should check whether those actions are still valid.
Pending actions should include enough context to verify them later: action type, amount, local time, game version, source event, and a unique identifier. This helps the server reject duplicates and understand whether the action fits the player's recent history.
On reconnect, reconcile instead of blindly uploading the new total. Send the list of pending actions and let the server replay them against the last trusted balance. If an action is duplicated, expired, too large, or impossible for that account state, reject it and keep the ledger explainable.
Log suspicious economy signals
Useful signals include impossible balance jumps, repeated failed receipt checks, many purchase retries, rollback patterns, and spending that does not match known income. One signal may be a bug. A pattern is more serious.
Log economy events with transaction IDs, source type, amount, balance before and after, build version, store, receipt status, and action taken. Avoid logging full payment details or secrets. The goal is to understand whether the economy path behaved correctly, not to collect sensitive billing data.
Do
- Keep the authoritative currency balance on the backend when the game has accounts or online features.
- Validate purchases with platform receipts before granting currency.
- Use protected memory and protected saves for local displays and offline-friendly flows.
- Use idempotent transaction IDs so retries cannot grant currency twice.
Don't
- Do not let the client directly set its own premium currency balance.
- Do not grant currency only because a local purchase callback says success.
- Do not ignore rollback, refund, duplicate receipt, or replay problems.