Testing Guide
Sandbox test card numbers for every payment outcome, without spending real money.
What this is and who needs it
When you are building or testing an integration, you do not want to spend real money to see if your code works. EPD’s sandbox lets you simulate every important payment outcome (successful charges, declines, expired cards, chargebacks) using fake card numbers that move no real funds.
If you are building, debugging, QA-ing, or running automated tests, this is the page you need.
How testing works
EPD does not have a separate sandbox URL. Instead:
- Sign in to the Merchant Portal and switch to your Demo Company; it was auto-created at sign-up and acts as the sandbox. Use the profile avatar (top right) to switch companies.
- Open the gear icon (top right) → Developer & Integrations → API Keys tab → Create API Key. Keys created inside the Demo Company are sandbox keys and start with
epd_test_sk_. - Send that key with your request. Any request made with a sandbox key runs in sandbox mode automatically.
- Capture one of the sandbox test card numbers below with EPD Elements (browser) or Inbound Card Capture (server-to-server): the card number decides the outcome when it’s later charged.
Sandbox test cards work only with sandbox keys and sandbox publishable keys. Capturing one against a live key returns a real-world outcome instead of a simulated one.
Sandbox test cards
When you capture a card through EPD Elements (browser SDK) or Inbound Card Capture (server-to-server), you type or send a real-format test card number. The outcome is decided by the card number when the card is later charged.
Use any future expiry (e.g. 12/2030) and CVV 999 with these numbers.
Success
| Card number | Network | Outcome |
|---|---|---|
4111 1111 1111 1111 | Visa | Charges succeed |
5431 1111 1111 1111 | Mastercard | Charges succeed |
3411 1111 1111 111 | American Express | Charges succeed |
6011 6011 6011 6611 | Discover | Charges succeed |
Decline & failure
A declined charge is not an error response. POST /v1/orders returns 201 with the order object; the order’s status is failed and its failure_code carries the normalized decline reason below (failure_reason is the human-readable companion). See Decline reason codes for the full set.
| Card number | failure_code | Outcome |
|---|---|---|
4000 0000 0000 0002 | processor_declined | Generic decline |
5105 1051 0510 5100 | processor_declined | Generic Mastercard decline |
4000 0000 0000 9995 | insufficient_funds | Card valid but no funds available |
4000 0000 0000 0069 | expired_card | Card has expired |
4000 0000 0000 0119 | processor_declined | Processing / network error |
4000 0000 0000 0127 | incorrect_cvv | CVV check failed |
4000 0000 0000 0259 | n/a | Charge succeeds; chargeback follows via webhook |
Any unrecognized card number defaults to a successful charge. Test cards from other providers’ docs, including the widely-copied 4242 4242 4242 4242, are not EPD test cards: they appear to vault and charge fine but can never exercise a decline path. Use the numbers above when you need to test failures.
End-to-end example: simulate a successful charge
curl https://api.epd.com/v1/customers \
-H "Authorization: Bearer $EPD_TEST_KEY" \
-H "epd-version: 2026-02-11" \
-H "Content-Type: application/json" \
-d '{
"email": "jane@example.com",
"first_name": "Jane",
"last_name": "Doe",
"phone": "+14155551234"
}'
Save the returned id as $CUSTOMER_ID.
Capture a test card number server-to-server with Inbound Card Capture, no browser required for this walkthrough. EPD tokenizes the card and returns a UUID id for the payment method; save it as $PM_ID.
curl -X POST "https://secure.epd.com" \
-H "Authorization: Bearer $EPD_TEST_KEY" \
-H "Content-Type: application/json" \
-d '{
"customer_id": "'"$CUSTOMER_ID"'",
"card": {
"number": "4111111111111111",
"exp_month": "12",
"exp_year": "2030",
"cvc": "999"
},
"set_as_default": true
}'
In a real checkout, capture the card in the browser with EPD Elements instead and attach the resulting card_token; see Card Vaulting.
Orders charge against a product, not a free-form amount. Create one and save its id as $PRODUCT_ID.
curl https://api.epd.com/v1/products \
-H "Authorization: Bearer $EPD_TEST_KEY" \
-H "epd-version: 2026-02-11" \
-H "X-EPD-Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" \
-d '{
"name": "Sandbox Test Product",
"sku": "sandbox-test-product",
"description": "A throwaway product for sandbox testing.",
"pricing": { "amount": 2000, "currency": "usd" },
"requires_shipping": false
}'
curl https://api.epd.com/v1/orders \
-H "Authorization: Bearer $EPD_TEST_KEY" \
-H "epd-version: 2026-02-11" \
-H "X-EPD-Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" \
-d '{
"customer_id": "'"$CUSTOMER_ID"'",
"payment_method_id": "'"$PM_ID"'",
"items": [
{ "product_id": "'"$PRODUCT_ID"'", "quantity": 1 }
]
}'
Example: simulate an insufficient-funds decline
Reuse the flow above but use the insufficient-funds test card number. This attaches a new payment method; use the id it returns as $PM_ID when you re-run the order step.
curl -X POST "https://secure.epd.com" \
-H "Authorization: Bearer $EPD_TEST_KEY" \
-H "Content-Type: application/json" \
-d '{
"customer_id": "'"$CUSTOMER_ID"'",
"card": {
"number": "4000000000009995",
"exp_month": "12",
"exp_year": "2030",
"cvc": "999"
}
}'
When you create the order, the charge is declined. The API still returns 201 with the order object, not an error envelope. The order comes back failed, with the decline reason on failure_code / failure_reason:
{
"data": {
"id": "6ba7b810-9dad-11d1-80b4-00c04fd430c8",
"status": "failed",
"amount": 2000,
"currency": "usd",
"failure_code": "insufficient_funds",
"failure_reason": "insufficient funds",
"request_id": "req_..."
}
}
What sandbox does not simulate
- Real settlement timing. Sandbox confirmations are immediate; live cards settle 1–3 business days later.
- Issuer-specific edge cases. Some banks reject otherwise-valid charges based on internal risk rules; sandbox cannot reproduce those.
- 3-D Secure / SCA challenges. EPD Commerce currently does not require 3DS for sandbox or live transactions through this API.
If you need an outcome not listed above, contact support; many additional decline codes can be enabled per-account.