A client-server protocol is the language your game uses to talk to your backend. Attackers can study that language with proxy tools, modified clients, logs, and scripts. Your goal is not to make every request invisible. Your goal is to make bad requests fail safely.
Assume requests can be changed
A player controls the device. That means request fields can be changed before they reach your backend. Item IDs, amounts, timestamps, match results, positions, cooldowns, and reward IDs should all be checked on the server.
Send intent, not final truth
A risky API says, "Set my balance to 10,000." A safer API says, "I want to claim reward X." The server then checks whether reward X exists, is active, was already claimed, and should grant currency.
This design keeps important state on the backend. The client requests actions, but the server decides outcomes.
"Set my gems to 10,000."
POST /player/set-balance { "gems": 10000 }The client sends the final value. If an attacker changes or replays this request, the backend may accept a fake balance.
"I want to claim daily reward X."
POST /rewards/claim { "rewardId": "daily_2026_06_06" }The client sends intent. The backend decides whether the action is valid and calculates the reward itself.
Use authentication and authorization
Authentication asks, "Who is this?" Authorization asks, "Is this player allowed to do this?" A signed-in player may be allowed to update their own loadout, but not another player's inventory or an admin-only event configuration.
Authorization checks should be close to the action. Do not only check that a user is logged in. Check that the user owns the character, item, match, clan role, purchase, or save slot they are trying to change.
Add replay resistance
Important requests should not work forever. Use short request windows, idempotency keys, nonces, and server-side action ledgers. If the same request is repeated, the backend should know whether it was already processed.
A replay attack is simple: an attacker captures a valid request and sends it again later. The body may be unchanged, the token may still look real, and HTTPS may have been used. If the backend only checks that the request is well formed, the action can happen twice.
Nonces work well when the server can issue a fresh one-time value before the action. For example, the server can return a nonce when the player opens the reward screen, starts a match, begins a purchase validation, or asks to craft an item. The nonce should be tied to the account, action, target resource, and a short expiration time.
The server should not only check that claimNonce exists. It should check who it was issued to, which action it belongs to, whether it expired, and whether it was already consumed. A nonce that was issued for daily_2026_06_07 should not be accepted for a battle-pass reward, purchase grant, or inventory craft.
Idempotency keys solve a related problem: safe retries. Mobile networks drop requests, players close apps, and clients retry after timeouts. You want the same legitimate retry to return the same result, not grant the reward twice.
The key detail is that idempotency keys must be scoped. A key should belong to one account, one route, one action, and often one payload hash. Do not let a client reuse the same key for different rewards, different purchases, or different inventory changes.
For purchases, replay resistance should include the platform receipt or transaction ID. A receipt should be recorded as consumed or fulfilled on the backend. If the same receipt arrives again, the backend should return the original result or reject it, not grant the same item again.
For match results, use a server-created match ID and submit window. The backend can require that the player was part of the match, the match is still waiting for results, the result version has not already been accepted, and the submitted data matches server-side rules. This stops an old valid result from being replayed after the match is closed.
Rate limit high-risk routes
Login, purchase validation, reward claims, matchmaking, inventory changes, and support actions need rate limits. Rate limits reduce brute force, scripts, accidental loops, and noisy abuse.
Good rate limits usually combine account, device, IP, and route. A purchase validation route may allow fewer attempts than a harmless profile refresh route. A reward claim route may allow retries, but only with the same idempotency key.
Keep production routes boring
Remove debug routes, staging hosts, admin tools, and test switches from release builds. Obfuscating route strings can reduce easy discovery, but production backend routes still need real validation.
Do
- Validate every important field on the server, including account, item, amount, timing, and state.
- Use authentication, authorization, rate limits, replay protection, and idempotency together.
- Design APIs around player intent, not client-provided final truth.
Don't
- Do not trust request bodies just because they came over HTTPS.
- Do not let the client send final balances, final ranks, or final rewards as truth.
- Do not expose debug, admin, or staging routes in production clients.