Advanced

Detecting replay attacks in game backend requests

A replay attack is simple in idea: a request that was valid once gets sent again. If the backend accepts it again, the attacker may repeat a reward, purchase claim, match result, or account action.

Why replay attacks matter in games

Games have many actions that should happen once. A daily reward should be claimed once per day. A purchase receipt should grant currency once. A match result should update rank once. A loot chest should not be opened again by resending the same request.

If your backend only checks that the request looks valid, repeated requests can become a real economy problem. The server needs memory. It must know whether an action has already been used.

Use idempotency keys

An idempotency key is a unique ID for one important action. The first time the server sees it, the server performs the action and stores the result. If the same key appears again, the server returns the stored result instead of doing the action again.

This is useful for purchases, reward claims, crafting, inventory updates, and match submissions. It also helps with honest retries when mobile networks fail halfway through a request.

A practical reward claim might store a small record like this on the backend:

reward-claim.yml
1idempotencyKey: "claim_daily_2026-06-06_player_123"
2accountId: "player_123"
3actionType: "daily_reward"
4rewardId: "daily_2026_06_06"
5status: "granted"
6grantedCurrency: 100
7createdAt: serverTime

If the same key arrives again, the server should not grant another 100 coins. It should return the stored result or say the reward was already claimed.

Use nonces and request windows

A nonce is a value that should only be used once. A timestamp says when the request was created. Together, they help the server reject old or repeated requests.

Use server time as the trusted reference. A request that is too old should be rejected or sent through extra verification. A nonce that was already used should not be accepted again.

For high-value actions, validate the request in this order:

  • Check the player session and account ID.
  • Check whether the request timestamp is inside a short window.
  • Check whether the nonce or idempotency key was already used.
  • Check whether the reward, match, purchase, or item is still valid.
  • Write the final result to the ledger before returning success.

Keep a server-side ledger

Valuable actions need a ledger. Store purchase transaction IDs, claimed reward IDs, match result IDs, battle pass claim IDs, and suspicious duplicate attempts. This makes your backend able to answer, "Have we already processed this?"

Do not rely on client truth

The client can generate a request, but the server should validate the action. Check account state, reward state, cooldowns, event windows, inventory, rank, and purchase records. Replay protection is strongest when it is part of a normal backend validation flow.

Handle retries kindly

Some repeated requests are honest. Mobile networks fail. Players close the app. A request may timeout after the server already processed it. Idempotency lets you support safe retries without giving duplicate rewards.

Do

  • Use idempotency keys for purchases, rewards, match results, and inventory changes.
  • Reject stale requests with server-side time windows.
  • Store enough request history to detect duplicate reward claims and repeated payloads.

Don't

  • Do not grant rewards only because a request has a valid shape.
  • Do not trust client timestamps as the only replay defense.
  • Do not let the same purchase, match result, or daily reward payload be claimed twice.
FAQ

Frequently asked questions.

Short answers to common questions developers ask after reading this article.