Testing data
Sandbox cards, bank accounts, and triggers for every gateway.
Sandbox runs on pk_test_ / sk_test_ keys and never touches real money. Which test cards work depends on the gateway behind your workspace — a Stripe card is rejected on an NMI workspace and vice versa.
Which gateway am I on?
Call GET /tokenization-key with a publishable key — the response carries processor_type. Then use the matching table below.
| processor_type | Gateway | Test data to use |
|---|---|---|
| nmi | NMI | Paysio (NMI) below |
| paysio | Paysio High Risk (white-label NMI) | Paysio (NMI) below — identical |
| stripe | Stripe | Stripe below |
| aptpay | Paysio Debit & Payouts | Debit & Payouts below |
If Smart Routing is enabled, a single POST /charges can land on either gateway depending on the card BIN — the response processorType tells you where it went. See Charges.
Paysio (NMI) cards
Also covers the paysio white-label gateway. Use expiry 10/29 (any future date works) and CVV 999.
| Brand | Number | CVV |
|---|---|---|
| Visa | 4111111111111111 | 999 |
| Mastercard | 5431111111111111 | 999 |
| Discover | 6011000991300009 | 999 |
| American Express | 341111111111111 | 9997 |
| Diners Club | 30205252489926 | 999 |
| JCB | 3541963594572595 | 999 |
| Maestro | 6799990100000000019 | 999 |
Paysio (NMI) ACH and outcome triggers
Save a bank account with type: "bank_account" on POST /customers/:id/payment-methods, then debit it with rail: "ach" on POST /charges.
| Field | Value |
|---|---|
| routing_number | 490000018 |
| account_number | 24413815 |
| account_type | checking |
On NMI the amount drives the outcome — the card number itself almost always approves:
| Outcome | How to trigger it |
|---|---|
| Approved | Any amount of 100 ($1.00) or more |
| Declined | Any amount below 100 — e.g. 50 |
| Fatal / gateway error | An invalid card number, e.g. 4111111111111129 |
| AVS match | billing_address_1: "888" and billing_postal_code: "77777" |
| CVV match | CVV 999 |
Amounts outside roughly $0.25–$130.00 are declined by the staging gateway as out of range, so keep test charges inside that window unless you are deliberately testing the amount decline. Do not combine a trigger amount with a card meant to trigger something else (an AVS/CVV card plus a decline amount) — the trigger amount bypasses normal authorization and the results contradict each other.
Stripe cards
Any future expiry and any CVC (4 digits for Amex). Card number drives the outcome.
| Number | Brand | Result |
|---|---|---|
| 4242424242424242 | Visa | Success |
| 4000056655665556 | Visa (debit) | Success |
| 5555555555554444 | Mastercard | Success |
| 2223003122003222 | Mastercard (2-series) | Success |
| 5200828282828210 | Mastercard (debit) | Success |
| 378282246310005 | American Express | Success |
| 6011111111111117 | Discover | Success |
| 4000000000000002 | Visa | Declined — generic_decline |
| 4000000000009995 | Visa | Declined — insufficient_funds |
| 4000000000009987 | Visa | Declined — lost_card |
| 4000000000009979 | Visa | Declined — stolen_card |
| 4000000000006975 | Visa | Declined — card_velocity_exceeded |
| 4000000000000069 | Visa | Expired card |
| 4000000000000127 | Visa | Incorrect CVC |
| 4000000000000119 | Visa | Processing error |
| 4242424242424241 | Visa | Incorrect number (fails Luhn) |
| 4000000000000101 | Visa | CVC check fails |
| 4000000000000036 | Visa | Postal code check fails |
| 4000000000000028 | Visa | Address line 1 check fails |
Stripe workspaces are card-only on Paysio — bank_account payment methods and rail: "ach" charges route through NMI or Debit & Payouts instead. For reference, Stripe's own ACH sandbox uses routing number 110000000 with account 000123456789 (success), 000222222227 (insufficient funds), 000111111113 (account closed), 000111111116 (no account), and 000333333335 (debit not authorized).
Debit & Payouts cards
Any future expiry and any CVC. Funds availability is what an POST /account-checks reports for a payout to that card.
| Number | Network | Type | Funds availability |
|---|---|---|---|
| 5333619503715702 | Mastercard | Debit | Immediate |
| 5113400335932393 | Mastercard | Debit | Next business day |
| 5374340047244865 | Mastercard | Prepaid | Immediate |
| 5407795354428594 | Mastercard | Prepaid | Next business day |
| 4111111110124511 | Mastercard | Credit | Not payout eligible |
| 4916900573237001 | Visa | Debit | Immediate |
| 4729260119078493 | Visa | Debit | Next business day |
| 4703063492858097 | Visa | Prepaid | Immediate |
| 4539803462059337 | Visa | Prepaid | Next business day |
| 4916451102321668 | Visa | Credit | Not payout eligible |
| 5525630721424710 | Interac | Debit | Immediate (CAD) |
Credit cards are accepted for charges but never for payouts: POST /account-checks returns ACCOUNT_NOT_ELIGIBLE and the saved method reports payout_eligible: false. Send a complete US billing address with every card — a card saved without a billing postal code cannot be paid out to later.
For ACH and RTP, use routing number (ABA) 021000021 with any 4–17 digit account number.
Payout failure triggers
Sandbox keys the payout outcome off the amount. Send one of these amounts (in cents) to POST /payouts and the matching failure arrives asynchronously on the payout.failed webhook with that error_code. Any other amount settles normally to payout.paid.
| amount | Code | Meaning |
|---|---|---|
| 51 | M001 | Account not eligible |
| 52 | M002 | Invalid input value |
| 53 | M003 | Declined |
| 54 | M004 | System error |
| 55 | M005 | Invalid input length |
| 56 | M006 | Invalid input format |
| 57 | M007 | Missing required input |
| 58 | M008 | Limit exceeded |
| 59 | M009 | Recipient account not eligible to receive funds |
| 60 | M010 | Amount exceeds network limit |
| 61 | M011 | Invalid card type |
| 62 | M012 | Reversal |
| 63 | M013 | Chargeback |
| 64 | M014 | Representment |
| 65 | M015 | Network error |
| 66 | M016 | Cutoff timing |
| 67 | M017 | Amount mismatch |
| 68 | M018 | Unspecified error |
| 69 | M019 | Service error |
| 72 | M021 | Cancelled by the receiving party |
// Trigger a declined payout (M003)
POST /payouts
{
"customer_id": "customer-uuid",
"payment_method_id": "instrument-id",
"amount": 53,
"rail": "card"
}
// → 200 { "status": "pending", ... }
// → webhook payout.failed { "error_code": "M003", "error_message": "Declined" }ACH debits return asynchronously with standard NACHA return codes — R01 insufficient funds, R02 account closed, R03 no account, R04 invalid account number, R07 authorization revoked, R08 payment stopped, R10 not authorized, R16 account frozen — delivered as payment.disputed with dispute_kind: "ach_return". RTP failures use ISO reason codes such as AC04 (account closed), AM04 (insufficient funds), and NOAT (account does not support this message type).
3-D Secure test cards
Paysio runs 3DS through its own authentication service, independent of the gateway that authorizes the charge, so 3DS has its own card set. These PANs pick the authentication outcome. Use any future expiry and any CVC.
| Number | Brand | Flow / result |
|---|---|---|
| 4147463011110134 | Visa | Frictionless approval — status Y, no challenge |
| 4016360000000493 | Visa | Challenge with an outcome picker — choose any result in the challenge window |
| 5239290700000102 | Mastercard | Frictionless approval — status Y |
| 5239290700000151 | Mastercard | Challenge, then approval — challenge password secret!33 |
| 4147463011110142 | Visa | Denied — status N |
| 4147463011110159 | Visa | Attempted — status A |
| 4147463011110175 | Visa | Rejected — status R |
| 4147463011110167 | Visa | Unavailable — status U |
| 5188340000000629 | Mastercard | Denied — status N |
| 5188340000000937 | Mastercard | Attempted — status A |
| 5188340000000952 | Mastercard | Rejected — status R |
| 5188340000000445 | Mastercard | Unable to authenticate — status U |
| 4111111111111111 | Visa | Not enrolled — tokenizes fine, but no authentication happens |
These 3DS PANs are not gateway test cards
They exercise authentication only — the POST /charges that follows is expected to be declined by NMI, Stripe, or Debit & Payouts. To test 3DS and a successful authorization end to end, run the flow twice: once with a 3DS card to check the three_ds result, once with the gateway test card above to check the charge.
They are also deliberately not Luhn-valid. The hosted card fields relax number validation in sandbox so they can be entered; your own form should do the same if it validates card numbers client-side.
redirect_url must be a real, publicly reachable URL that accepts the challenge POST — localhost is rejected. Use a tunnel when testing locally.
Full flow and the fields to forward on the charge: 3D Secure.