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:

  1. 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.
  2. Open the gear icon (top right) → Developer & IntegrationsAPI Keys tab → Create API Key. Keys created inside the Demo Company are sandbox keys and start with epd_test_sk_.
  3. Send that key with your request. Any request made with a sandbox key runs in sandbox mode automatically.
  4. 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 numberNetworkOutcome
4111 1111 1111 1111VisaCharges succeed
5431 1111 1111 1111MastercardCharges succeed
3411 1111 1111 111American ExpressCharges succeed
6011 6011 6011 6611DiscoverCharges 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 numberfailure_codeOutcome
4000 0000 0000 0002processor_declinedGeneric decline
5105 1051 0510 5100processor_declinedGeneric Mastercard decline
4000 0000 0000 9995insufficient_fundsCard valid but no funds available
4000 0000 0000 0069expired_cardCard has expired
4000 0000 0000 0119processor_declinedProcessing / network error
4000 0000 0000 0127incorrect_cvvCVV check failed
4000 0000 0000 0259n/aCharge 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

Create a customer
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.

Attach a sandbox card

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.

Create a product

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
  }'
Create an order
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.