Stripe Test Mode and Sandboxes: Test Payments Without Real Money
How to test Stripe payments safely: test keys vs live keys, Stripe sandboxes, the test card numbers for success, declines and 3D Secure, testing webhooks locally with the Stripe CLI, simulating subscriptions with test clocks, and the checklist for switching to live mode.
Before your app takes real money, you need to test it with fake money. Stripe makes this easy: everything you do in test mode or a sandbox looks real — payments, customers, subscriptions, webhooks — but no card is charged.
Test keys vs live keys
Every Stripe account has two sets of API keys:
| Test | Live | |
|---|---|---|
| Publishable key | pk_test_... |
pk_live_... |
| Secret key | sk_test_... |
sk_live_... |
| Charges real cards | No | Yes |
Use test keys in development and staging, live keys only in production. Keep the secret key on your server — never in frontend code. (Hide API Keys in an AI-Built App)
Sandboxes
Stripe now offers sandboxes: isolated test environments inside your account. You can create several — one for local development, one for CI, one for a teammate — so test data and settings don't collide. Stripe recommends general sandboxes for new integrations, and the older "test mode" sandbox for existing integrations that depend on it. Find them under Sandboxes in the Dashboard's account picker.
Test card numbers
In test mode, use these with any future expiry date and any 3-digit CVC:
| Card number | What happens |
|---|---|
4242 4242 4242 4242 |
Payment succeeds |
4000 0025 0000 3155 |
Requires 3D Secure authentication |
4000 0000 0000 0002 |
Declined (generic) |
4000 0000 0000 9995 |
Declined (insufficient funds) |
Stripe's testing docs list many more, including specific countries, disputes and other payment methods. Never use real card numbers in test mode — Stripe rejects them.
Test the unhappy paths too: declines and 3D Secure are where integrations usually break.
Test webhooks locally
Your app learns about payments through webhooks. Stripe can't reach localhost, so use the Stripe CLI to forward events:
stripe login
stripe listen --forward-to localhost:3000/api/webhooks/stripe
The CLI prints a webhook signing secret (whsec_...) — put it in your local environment so signature verification passes. Then trigger events on demand:
stripe trigger checkout.session.completed
stripe trigger invoice.payment_failed
(Test Webhooks Locally, Handle Webhooks Reliably)
Test subscriptions with test clocks
Waiting a month to see if renewal works isn't practical. Test clocks let you create a customer and subscription attached to a simulated clock, then move time forward to trigger renewals, trial endings and failed payments in seconds. (Stripe Subscriptions Explained)
Before you switch to live
- Successful payment, decline and 3D Secure all handled.
- Webhook endpoint created in live mode with its own signing secret (test and live secrets differ).
- Your app grants access from the webhook, not just the redirect page.
- Live keys only on the production server; test keys everywhere else.
- Products and prices recreated in live mode (they don't copy over).
- Receipts, tax and refund settings checked.
Then make one small real purchase yourself and refund it.
The summary
- Test keys (
sk_test_) and sandboxes never move real money. 4242 4242 4242 4242succeeds; test declines and 3D Secure too.- Use
stripe listenfor local webhooks and test clocks for subscriptions.
EasySpawn runs your app on a server with HTTPS from day one, so your live Stripe webhook endpoint just works — and Claude Code can wire up and test the whole flow. See how it works or join the waitlist.
Related: Add Stripe Payments to an AI-Built App · Stripe Payment Links vs Checkout · Stripe vs Paddle vs Lemon Squeezy · Idempotency Keys
Keep reading
What Is UTF-8? Character Encoding and Garbled Text (Mojibake) Explained
UTF-8 is the character encoding that lets computers store every language and emoji as bytes — and it's the default almost everywhere now. How encoding works, why you see garbled text like ’ or é, and how to fix it in HTML, databases, CSV files and code.
"User Is Not in the Sudoers File": What It Means and How to Fix It
"username is not in the sudoers file. This incident will be reported." means your Linux user isn't allowed to run commands as root with sudo. How to add a user to the sudo (or wheel) group from root, edit sudoers safely with visudo, recover when you've locked yourself out, and the safer alternatives to giving out sudo.