Sandbox purchases produce real events and webhooks, flagged
"environment": "Sandbox", so you can exercise everything end to end. Apple
runs sandbox subscriptions on an accelerated clock (a month renews in minutes),
which is the fastest way to watch a full renewal lifecycle.
Testing
Test mode vs live, store sandbox accounts, seeded data, and a go-live checklist.
CashSDK gives you a full test environment that mirrors production: separate data, separate server keys, and the same APIs, so you can validate the entire purchase flow before a single real charge.
Sandbox vs Production#
Do not build two variants of your app. There is one publishable key per app
(csk_pk_…), it has no test/live split, and it belongs in every build
configuration you ship:
CashSDK.configure(publishableKey: "csk_pk_…")
CashSDK.configure(context = this, publishableKey = "csk_pk_…")
The reason is that the choice does not exist to make. TestFlight distributes the exact binary you submit to the App Store, and Play internal testing distributes the exact APK from your listing. One key is compiled into both, so pinning it to one environment would lock out every user of the other.
The store decides which environment a purchase belongs to, per purchase:
| How the build runs | Store environment |
|---|---|
| From Xcode, or installed via TestFlight | Apple Sandbox |
| Installed from the App Store | Production |
| Google Play internal/closed testing track with a licence tester | Play test purchase |
CashSDK reads that out of the store's own signed payload, so a client cannot
spoof it. Entitlements and consumable balances are resolved per environment:
a Sandbox purchase never grants Production access to the same user, a coin pack
bought in TestFlight is not spendable in the App Store build, and sandbox revenue
never mixes into your production numbers. The SDK works out which environment it
is running in on its own, from Transaction.environment on iOS and from the
verify response on Android, and asks for that one on every subsequent read.
You almost never need to set environment: in configure(...). Set it only to
pin a QA build that must read one environment before it has made any purchase at
all; a pinned value always wins over what the store reports, so a wrong one shows
an empty entitlement list.
Your server keys are still split, and there you want them to be:
csk_sk_test_… reaches Sandbox data, csk_sk_live_… reaches Production, and
neither can reach the other. Point your staging backend at the test key and no
config mistake can have it writing to real customers. See
Authentication.
Store sandbox accounts#
Native store purchases in test mode require a store sandbox tester:
- Apple: create a Sandbox Apple Account in App Store Connect (Users and Access → Sandbox) and sign into it on the device under Settings → Developer.
- Google: add the tester's Google account to your app's license testers in the Play Console, and use an internal testing track.
Sandbox subscriptions renew on an accelerated clock (minutes, not months), so you can watch renewals, expirations, and grace periods play out quickly.
Seeded sandbox data#
Test mode comes pre-populated with real seeded rows (apps, a product catalog,
paywalls, customers, and transactions) so the dashboard, APIs, and analytics
are fully populated on day one. It's real data in your database, not mocks, so
GET /v1/apps/{app}/customers and friends return exactly what production would.
Verify your setup#
The CLI checks keys, store credentials, webhook endpoints, and catalog wiring in one pass.
cashsdk doctor
See exactly what's left before go-live, live-computed from your workspace.
cashsdk checklist
The same data is available at GET /v1/apps/{app}/setup/checklist.
Confirm your App Store Server Notification pipeline end to end with the MCP
request_apple_test_notification tool. Apple sends a test notification to
your instance, which verifies it and emits the corresponding webhook.
request_apple_test_notification(app="my-app")
Use send_test_webhook (or the dashboard) to confirm your endpoint verifies
the CashSDK-Signature and handles the event. See Webhooks.
Go-live checklist#
Replace csk_sk_test_… with csk_sk_live_… on your server, sourced from an
environment variable, never hard-coded. Your app does not change: the
publishable key it already ships is the one that goes live with it.
Production App Store Connect and Google Play credentials are connected and
validated (cashsdk doctor passes).
App Store Server Notifications and Google Play RTDN target your live instance, and your webhook endpoint is registered and verifying signatures. A separate Mac App Store record needs the same notification URL and its Mac Apple App ID before Production purchases from it verify.
Buy and refund once in production, confirm the entitlement grants and revokes, and check the event lands on your webhook.