Skip to content
Sandbox open: magic test cards, hosted checkout and signed webhooks · 55 payment methods in the catalogue, cards to crypto · Built and hosted in the EEA · Read the API reference at /developers

Sandbox open: magic test cards, hosted checkout and signed webhooks

Documentation

Sandbox

Sandbox and test cards

Every card number the simulator understands, the metadata keys that override them, and how the challenge page and the async rails behave.

Updated

Any cp_*_test_ key routes to the simulator. The table below is rendered from the same module the simulator reads, so it is the behaviour, not a description of it. The endpoints themselves are documented under sandbox endpoints.

Magic cards

Tokenise one of these with POST /v1/sandbox/tokens, then create a payment with the token. Any future expiry and any 3 or 4 digit security code are accepted; a number that is not in this table is refused with invalid_card_number.

9 magic cards, rendered from the simulator's own module. Any future expiry and any 3 or 4 digit security code are accepted.
Card numberBrandResulting statusWhat the simulator does
4242 4242 4242 4242visacapturedAuthorised, then captured when capture_method is automatic.
4000 0000 0000 0002visadeclineddecline_code generic_decline, decline_is_soft false — do not retry with this card.
4000 0000 0000 9995visadeclineddecline_code insufficient_funds, decline_is_soft true — safe to retry.
4000 0000 0000 0069visadeclineddecline_code expired_card, decline_is_soft false — do not retry with this card.
4000 0000 0000 0127visadeclineddecline_code incorrect_cvc, decline_is_soft false — do not retry with this card.
4000 0000 0000 0119visafailed422 processing_error with code payment_failed; response code 96.
4000 0000 0000 3220visarequires_actionnext_action.redirect to the simulated challenge page, then approved.
4000 0000 0000 3063visarequires_actionnext_action.redirect to the simulated challenge page, then declined.
4000 0000 0000 0259visaprocessingResolves to captured after 30 s, or immediately on POST /v1/sandbox/advance/:id.

The same rows are available as JSON from GET /v1/sandbox/test-cards, so a test fixture can be generated rather than copied.

Metadata overrides

The card decides the outcome for card payments. To force an outcome on any method — including alternative methods, which have no card number — put one of these keys in metadata on the payment. They are ignored outside test mode.

metadata keyAcceptsEffect
_cp_test_outcomeapproved · declined:<decline_code> · failed · requires_actionOverrides the card's planned outcome. With declined:<decline_code> the soft codes are insufficient_funds, issuer_unavailable, try_again_later, sca_required; every other code is hard.
_cp_test_latency_msintegerDelays the connector response by that many milliseconds before it answers.
_cp_test_http_errorHTTP statusAnswers with that status and code connector_http_error instead of processing. The payment stays pending.
curl https://cleared-pay.com/api/v1/payments \
  -H "Authorization: Bearer $CLEARED_SECRET_KEY" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{
    "amount": 2000,
    "currency": "eur",
    "reference": "ORDER-10432",
    "payment_method": { "type": "ideal" },
    "return_url": "https://merchant.example/return",
    "metadata": { "_cp_test_outcome": "declined:stolen_card", "_cp_test_latency_ms": 800 }
  }'

The simulated challenge page

A card that requires authentication answers with status: "requires_action" and a next_action.redirect.url pointing at /3ds-test/:sessionId. The session id is the connector's own transaction reference, so you can correlate the two. The page has three buttons:

  • Approve runs the card's planned outcome — so 4000 0000 0000 3220 captures and 4000 0000 0000 3063 declines.
  • Decline records a soft sca_required decline with three_ds.status: "failed". Soft, because a fresh challenge is the correct retry.
  • Timeout fails the payment: status failed, not declined, because the issuer never answered.

After any of the three the payer is returned to your return_url. Read the payment back when they land — the redirect is not proof of an outcome.

Alternative methods

Methods have no magic numbers; they have rails, and each rail behaves the way it does in production.

  • Redirect rails — iDEAL, Bancontact, BLIK, Przelewy24, Trustly, PayPal, Klarna, MB WAY, open banking, the e-wallets, vouchers and crypto — go straight to requires_action with a redirect. Follow it, and the payment resolves.
  • Async rails — SEPA Direct Debit, SEPA Credit Transfer, Multibanco and Pix — stay processing for 30 seconds and then capture, which is the shape of a real bank transfer without the wait.
  • Wallets — Apple Pay and Google Pay need a real device flow. They are not simulated, and the hosted checkout page leaves them out in test mode rather than faking a result.

No method needs a token. Set payment_method.type and a return_url, and use the metadata overrides above to force a decline or a failure.

Advancing and simulating

Waiting is the slowest thing in a test suite, so nothing needs waiting for. Force a processing payment to resolve, and fire an event at your receiver without making a payment at all:

# resolve an async payment now instead of in 30 s
curl -X POST "https://cleared-pay.com/api/v1/sandbox/advance/pay_01JBXQ6P9R4T2V7XZ1BD5GH8KM" \
  -H "Authorization: Bearer $CLEARED_SECRET_KEY"

# store a real, signed event and deliver it inline; the attempts come back in the response
curl https://cleared-pay.com/api/v1/sandbox/simulate-webhook \
  -H "Authorization: Bearer $CLEARED_SECRET_KEY" \
  -H "Content-Type: application/json" \
  -d '{"type":"payment.captured","resource":"pay_01JBXQ2M4K7T9V3ZC8HR5N6PWD"}'

advance answers 422 payment_not_advanceable for a payment that is not processing. A simulated event is stored and signed like any other, so it is the right way to test verification — including the rotation case, where the header carries two v1= values.