Advanced

Secure receipt validation for Google Play, Apple, and Steam purchases

In-app purchases are one of the highest-value actions in a game. A client-side purchase callback is useful, but it should not be the final reason premium currency or items are granted.

The client starts the purchase, the backend trusts the store

The normal safe flow is simple. The client starts the purchase through Google Play, Apple, or Steam. The client sends the receipt, purchase token, or transaction information to your backend. The backend asks the store whether the purchase is real. Only then does the backend grant the reward.

This keeps store credentials and private keys on the server. It also gives you one trusted place to prevent duplicate claims and handle refunds.

A simple backend flow can look like this:

receipt-validation-flow.txt
1client -> backend: purchaseToken, productId, platform
2backend -> store: verify token with private server credential
3store -> backend: valid purchase, transactionId, productId
4backend: check transactionId was not granted before
5backend: grant item or currency once
6backend -> client: purchase confirmed

Grant each transaction once

Every store purchase has some kind of transaction identity. Store that identity in a purchase ledger. If the same transaction is sent again, return the existing result instead of granting the reward twice.

This protects against replay attempts and honest retry problems. A phone may lose connection after the backend grants the purchase. The next retry should not duplicate the reward.

Store a purchase ledger entry with enough detail to answer future questions:

  • Platform, such as Google Play, Apple, Steam, or another store.
  • Transaction ID or purchase token.
  • Game account ID that received the reward.
  • Product ID and granted item or currency amount.
  • Validation status, grant status, and timestamps.
  • Refund, chargeback, or subscription status when available.

Bind purchases to accounts carefully

Think through account switching. What happens if a player buys on one account and then sends the receipt from another account? What happens if the same platform account is linked to multiple game accounts? Your backend should have a clear rule before release.

Handle refunds and chargebacks

Secure validation is not only about granting purchases. It is also about later changes. Stores can report refunds, chargebacks, subscription expiration, or pending payments. Your backend should update entitlement state when those changes matter.

Keep store secrets out of the client

Store API credentials, service account keys, shared secrets, and private tokens must not be shipped inside Unity builds. Anything in the client can be extracted. Put those secrets on the backend and let the backend talk to the store.

The Unity client should only send purchase proof it received from the platform. It should never contain the service account JSON, App Store shared secret, Steam publisher key, or any credential that can validate purchases by itself.

Log failed validation attempts

Log invalid receipts, repeated transaction IDs, account mismatch cases, and unusual purchase timing. One failed validation may be harmless. A pattern across many accounts can reveal abuse.

Do

  • Validate receipts or purchase tokens from your backend, not only from the Unity client.
  • Store transaction IDs so the same receipt cannot be claimed twice.
  • Handle refunds, chargebacks, pending purchases, and subscription changes.

Don't

  • Do not put store API credentials or private keys inside the game client.
  • Do not grant premium currency only because a local purchase callback fired.
  • Do not ignore duplicate receipt attempts or account switching edge cases.
FAQ

Frequently asked questions.

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