Testing
Frame's test mode lets you exercise your integration without moving real money. Test API keys + the test cards on this page drive the simulated paths — successful charges, specific declines, 3D Secure outcomes, and ACH flows.
A few non-negotiables before you start:
- Use test API keys for everything in test mode. Test cards rejected outright under production keys; production cards rejected under test keys. Mixing breaks signature validation and PCI compliance.
- Never use real card details in test mode. Frame's platform agreement prohibits it. Production card data is for production charges only — even when "you just want to confirm the integration works."
- Don't load-test through the API. Test mode rate limits are stricter than production. For real load testing, see the dedicated load-testing approach (talk to Frame support).
When you're ready to ship, swap test keys for production keys and the same code paths run against real card networks.
How test cards work
Frame's processor stubs match the card number you submit and return a deterministic outcome — success for the standard test cards, specific failure_code values for the decline cards, 3DS challenge flows for the 3DS cards.
You don't need to worry about PCI compliance when submitting test card numbers directly via the API. In production, frame-js handles the card data before it reaches your server; in test mode, raw card numbers via API are acceptable because nothing real is at stake. By default, a PaymentMethod isn't attached to a Customer — handle the association in your code if your use case needs it.
Successful card payments
Use any of these to simulate a successful card charge:
| Brand | Number | CVC | Expiration |
|---|---|---|---|
| Visa | 4242424242424242 | Any 3 digits | Any future date |
| Visa (debit) | 4000056655665556 | Any 3 digits | Any future date |
| Mastercard | 5555555555554444 | Any 3 digits | Any future date |
| Mastercard | 2223003122003222 | Any 3 digits | Any future date |
| Mastercard (debit) | 5200828282828210 | Any 3 digits | Any future date |
| Mastercard (prepaid) | 5105105105105100 | Any 3 digits | Any future date |
| American Express | 378282246310005 | Any 4 digits | Any future date |
| American Express | 371449635398431 | Any 4 digits | Any future date |
| Discover | 6011111111111117 | Any 3 digits | Any future date |
| Discover | 6011000990139424 | Any 3 digits | Any future date |
| Discover (debit) | 6011981111111113 | Any 3 digits | Any future date |
Any of these clear with status: succeeded and no failure_code. Use them for happy-path integration testing.
Declined card payments
To exercise your error-handling code, use these cards. Each one returns a specific failure_code you can match on in your decline-handling logic:
| Brand | Number | Failure code |
|---|---|---|
| Visa | 4726840306461785 | expired_card |
| Visa | 4726840756845529 | incorrect_cvc |
| Visa | 4726849921107855 | processing_error |
| Visa | 4726843991344803 | incorrect_number |
| Visa | 4726846912498186 | insufficient_funds |
| Visa | 4726843338079575 | stolen_card |
| Visa | 4726848138487043 | generic_decline |
| Visa | 4726848912206759 | card_velocity_exceeded |
Each failure_code lands on the resulting Charge record (and on the charge.failed webhook). For the full enum of decline codes Frame can return + the merchant-facing handling for each, see decline codes and handle declines.
If you need to test a generic-decline path beyond the codes above, use any non-test card number — Frame test mode rejects unrecognized cards with a generic decline rather than running them.
3D Secure test cards
3D Secure is the network-level cardholder-authentication protocol. Use these cards to exercise the various 3DS outcomes — the integration-side handling is covered in handle 3D Secure.
| Number | Outcome | Description |
|---|---|---|
4000000000003220 | Succeeds | Charge requires 3DS2 authentication and completes successfully. |
4111110116638870 | Succeeds | 3DS authentication runs via the Frictionless flow (no customer challenge UI). |
4111111738973695 | Declined | 3DS Frictionless flow runs, but the charge declines post-authentication with card_not_enrolled. |
4000008400001629 | Declined | 3DS challenge UI shows; charge declines after authentication. |
For the conceptual model behind these flows — what 3DS actually does, when it triggers, the liability shift — see 3D Secure.
ACH test accounts
Bank-account payments via ACH have their own test fixtures. Use these account/routing pairs to drive the simulated outcomes:
| Account number | Routing number | Behavior |
|---|---|---|
000123456789 | 110000000 | Payment succeeds |
000111111113 | 110000000 | Payment fails — account closed |
000111111116 | 110000000 | Payment fails — account not found |
000222222227 | 110000000 | Payment fails — insufficient funds |
000333333335 | 110000000 | Payment fails — debits not authorized |
ACH settles asynchronously even in production — the test fixtures preserve that timing. Your handler should react to the eventual transfer.failed or transfer.succeeded webhook, not the immediate create response.
Rate limits in sandbox mode
Sandbox's rate limiter is tighter than live — 50 requests/minute on most endpoints versus 160 in live mode. Hitting it returns 429 Too Many Requests. See Rate limits for the full table.
- Don't use sandbox for load testing. The limiter is intentionally tighter to protect shared sandbox infrastructure.
- Errors you see at high request volume in sandbox are usually rate-limiter artifacts, not integration bugs.
- If you actually need to load-test your integration's behavior under high request volume, talk to Frame support about lifting limits for a specific window or using a dedicated load-testing environment.
Switching to production
When your integration's ready:
- Replace test publishable + secret API keys with production keys.
- Swap any hardcoded test card numbers for real frame-js card collection — never log production card data anywhere your application can read it.
- Verify webhook endpoints are pointed at your production handler (sandbox + production endpoints are separately configured).
- Run a small live transaction with your own card to confirm the end-to-end flow.
If anything misbehaves in production that worked in test mode, the most common gaps are: webhook signature secrets differing between environments, rate-limit assumptions that don't translate, and 3DS-required transactions that test mode skipped.
Gotchas
Symptom: test cards rejected with "invalid card number" in production mode. Why: you're using production API keys with test card numbers. Fix: use test keys in test mode; never mix.
Symptom: the decline-code Frame returned doesn't match the legacy mapping for a given test card. Why: the test card → failure_code mappings here track frame's current Charge::FailureMessages enum. If you see drift, the enum is the source of truth — file an issue for the doc to catch up.
Symptom: ACH test charge stays in pending for a long time. Why: ACH is asynchronous; the test fixtures honor that timing rather than collapsing to instant settlement. Fix: wait for the webhook, or poll the resource. Don't treat pending as failure.
Symptom: you can't trigger a 3DS challenge with the Visa card 4242.... Why: 4242... is the always-succeeds-no-challenge card. Use the 3DS test cards above to trigger authentication. Fix: pick the card matching the outcome you want to test.
Symptom: rate-limit errors in test mode that wouldn't happen in production. Why: test mode's limiter is intentionally tighter. Fix: slow your test request rate, or talk to Frame support if testing genuinely needs higher volume.
Reference
For the underlying primitives, see transfers, decline codes, and 3D Secure.