Skip to main content

Sandbox & Testing

The sandbox is the same platform as production, running the same code, with payment providers replaced by a simulator. Use it to build and test your integration; no money moves.


Sandbox vs Production​

SandboxProduction
Base URLhttps://sandbox.4pay.online/api/v1https://4pay.online/api/v1
Payment pagehttps://pay.sandbox.4pay.onlinehttps://pay.4pay.online
CredentialsSeparate key and organization idSeparate key and organization id
Real charges❌ Never✅ Real money
Webhooks✅ Sent to your terminal's notify_url✅ Sent to your terminal's notify_url
Different hosts, not different keys

The environments are separate deployments on separate hostnames, and credentials do not carry across: a sandbox key is meaningless in production and the other way round. Keys have no prefix that tells them apart — keep them in separate configuration.

Inside a host, env in the request body picks the environment of the transaction itself, and it is required. A ?env=test query parameter only scopes searches: a create carrying just that produces a production transaction. See Transactions.


Getting sandbox credentials​

Ask your platform operator. They create a partner account for you in a sandbox organization and hand you two values together — the API key and the organization id. Both are needed on every request.


Making a test transaction​

curl -X POST https://sandbox.4pay.online/api/v1/transactions \
-H "Content-Type: application/json" \
-H "x-api-key: YOUR_SANDBOX_KEY" \
-H "x-organization-id: YOUR_SANDBOX_ORGANIZATION_ID" \
-d '{
"params": {
"type": "payment",
"amount": "1000",
"currency": "USD",
"txid": "sandbox-test-001",
"env": "test",
"hosted": true,
"returnUrl": "https://yoursite.com/success",
"failUrl": "https://yoursite.com/fail"
}
}'

You get back a widget_url. Open it in a browser to see the hosted payment form, and pay with one of the cards below.


Test cards​

The simulator does not classify a card by its BIN the way public gateways do. It compares the whole tuple — number, CVV, cardholder and expiry — against a fixed list. One character of difference is refused with unexpected card.

Card numberCVVCardholderExpiryResult
2201382000000013283IVAN IVANOV12 / 20✅ Settles — charging, then charged within about a minute
2201382000000021749IVAN IVANOV12 / 20❌ rejected by the provider
2201382000000047480IVAN IVANOV12 / 20↩️ Settles, then refunds
2201382000000039572IVAN IVANOV12 / 20❌ failed
2201382000000054111IVAN IVANOV12 / 20⏸ Payout stuck in payout_suspended

Anything else — including the Visa and Mastercard test numbers other gateways use — ends as rejected with unexpected card.

"money_storage": {
"cardnumber": "2201382000000013",
"cvv": "283",
"cardholder": "IVAN IVANOV",
"exp_year": "20",
"exp_month": "12"
}

Note exp_year is two digits, and the cardholder is upper-case. The SDK exports these as constants (sandbox.CARD_CHARGE and friends) so you do not retype them.

Payouts​

FieldValueResult
bank_id200000000001Payout settles
bank_id200000000002Payout refused
customer (SBP)John DoeSettles; any other name is refused

Charging without a browser​

For automated tests, skip the payment page and charge the card server-to-server:

curl -X POST https://sandbox.4pay.online/api/v1/transactions \
-H "Content-Type: application/json" \
-H "x-api-key: YOUR_SANDBOX_KEY" \
-H "x-organization-id: YOUR_SANDBOX_ORGANIZATION_ID" \
-d '{
"params": {
"type": "payment",
"amount": "1000",
"currency": "USD",
"txid": "sandbox-test-002",
"env": "test",
"show_widget": false,
"money_storage": {
"cardnumber": "2201382000000013",
"cvv": "283",
"cardholder": "IVAN IVANOV",
"exp_year": "20",
"exp_month": "12"
},
"returnUrl": "https://yoursite.com/success",
"failUrl": "https://yoursite.com/fail"
}
}'

It returns charging and reaches charged on its own within about a minute. Both redirect URLs are required here — a server-to-server payment without them is created and immediately rejected.

Card data puts you in PCI DSS scope

Passing a real card number through your own server means the card data environment is yours to secure and audit. In the sandbox this is a convenience for tests; in production, use the hosted flow unless you already hold a PCI DSS attestation.


Testing webhooks​

Expose your local server with a tunnel:

ngrok http 3000
# → Forwarding: https://abc123.ngrok.io → localhost:3000

Ask your operator to set https://abc123.ngrok.io/webhooks/payment as the notify_url on your sandbox terminal, with a webhook secret — without one the platform sends no signature and you cannot tell a genuine delivery from a forged one. Details in Webhooks.


What is not simulated​

Provider-specific behaviourThe simulator stands in for every provider. Rail-specific quirks — 3DS variants, local wallet redirects, bank cut-off times — appear only in production
Settlement timingSandbox payments settle in seconds; real rails take minutes to days
FeesFee calculation runs, but with whatever rates the sandbox organization has configured

Going live​

  1. Get production credentials from your operator — a new key and organization id.
  2. Change the host to https://4pay.online.
  3. Change env in the request body to "prod".
  4. Point notify_url at your production endpoint, with its own secret.
  5. Run one real payment for a small amount, and refund it.

Steps 2 and 3 are separate on purpose. The host decides which platform you are talking to; env decides which side of that platform the transaction lives on. Getting them out of step is the mistake that produces a "test" payment on a live rail.