Receipt validation

Validate App Store and Google Play purchases server-side, protect against replay and fraud, and ingest store notifications.

A purchase isn't real until it's verified against the store. Client SDKs report a purchase for a snappy UI, but the authoritative grant comes from server-side validation. CashSDK validates every receipt for you and keeps entitlements in sync.

Never grant paid access based on a boolean returned by the client. A jailbroken device, a tampered build, or a man-in-the-middle can fake it. Authorize against a validated receipt or a verified webhook.

Why validate server-side#

  • Authenticity — confirm the receipt was really issued by Apple or Google.
  • Replay protection — reject receipts that have already been redeemed or reused across accounts.
  • Fraud signals — surface refunds, revocations, sandbox receipts sent to production, and mismatched bundle ids.
  • Truth over time — renewals, cancellations, and refunds change validity after the sale; only the server sees them.

Validate a receipt#

1
Collect the receipt

The SDK obtains the store receipt (Apple) or purchase token (Google) during checkout. Send it to your backend, or let the SDK forward it automatically.

2
Call the validation endpoint

Your backend posts the receipt with the customer's app_user_id.

bash
curl https://api.cashsdk.com/v1/receipts/validate \
  -H "Authorization: Bearer csk_sk_..." \
  -H "Content-Type: application/json" \
  -d '{
    "platform": "apple",
    "receipt": "MII...",
    "app_user_id": "u_812"
  }'
3
Grant access from the response

CashSDK returns the validated entitlements. Grant access from these, not from the client.

json
{
  "valid": true,
  "app_user_id": "u_812",
  "entitlements": {
    "pro": {
      "active": true,
      "product_id": "pro_monthly",
      "expires_at": "2026-08-17T00:00:00Z",
      "store": "app_store",
      "environment": "production"
    }
  },
  "transaction": {
    "id": "txn_9ab21",
    "original_transaction_id": "1000000812",
    "is_trial": false
  },
  "fraud": {
    "replayed": false,
    "sandbox_in_production": false,
    "score": 0.02
  }
}
platformstringrequired

"apple" or "google". Determines how the receipt is verified upstream.

receiptstringrequired

The base64 App Store receipt or the Google Play purchase token.

app_user_idstringrequired

Your stable identifier for the customer. Ties the purchase to a customer so entitlements sync across devices and platforms.

validboolean

Whether the receipt authenticated and is not replayed or revoked.

entitlementsobject

Map of entitlement id to its active state, product, and expiration.

fraudobject

Signals such as replay detection, sandbox-in-production, and a risk score.

Signature & replay protection#

CashSDK verifies each receipt's cryptographic signature with Apple and Google, then records its transaction id. A receipt (or its original_transaction_id) can only ever be bound to one customer — a second attempt on a different app_user_id is rejected as a replay and flagged in the fraud block.

Ingest store notifications#

Validation is a point-in-time snapshot. To stay current as renewals and refunds happen, CashSDK ingests App Store Server Notifications V2 and Google Play Real-time Developer Notifications. Point both at your CashSDK instance:

  • Apple — set the Server Notification URL in App Store Connect to your instance's notification endpoint.
  • Google — configure a Pub/Sub topic for Real-time Developer Notifications linked to the same instance.

CashSDK verifies each notification, updates the customer's entitlement, and emits the matching webhook event to your backend. Test the Apple path with the MCP request_apple_test_notification tool — see Testing.

Next steps#