Testing in the Sandbox
Simulation mode, X-Sim-Scenario headers, test cards, and scripting full payment flows.
The sandbox is a full simulation of the production Kvell stack β same endpoints, same request signing, same response shapes β with the acquiring bank, 3DS challenge, and payout rails replaced by deterministic mocks. Nothing here moves real money or reaches a real bank, so it's where you build and test your entire integration before requesting production access.
Sandbox vs. production
| Sandbox | Production | |
|---|---|---|
| Base URL | https://api.pay.sandbox.kvell.group | https://api.pay.kvell.group |
| API keys | pk_demo_β¦ / sk_demo_β¦ | pk_live_β¦ / sk_live_β¦ |
| Bank, 3DS, payout rails | Deterministic mocks | Real acquirer, ACS, FAST/EFT/OCT |
X-Sim-Scenario header | Honored | Ignored |
| Money movement | None β simulated | Real |
Grab your sandbox keys from the dashboard (Developers β API Keys). Request signing works identically in both environments β see Authentication & Request Signing if you haven't wired that up yet.
Forcing an outcome with X-Sim-Scenario
Every sandbox request accepts an optional X-Sim-Scenario header. Set it on the call that starts a flow β a payment session or card charge for payment scenarios, a payout create for payout scenarios β and Kvell drives every downstream mock (acquirer, 3DS ACS, FAST/EFT/OCT) to the outcome you asked for. Omit it and you get a mostly-approves default instead.
Scenario catalog
| Scenario | Exercises |
|---|---|
payment-success-3ds | Full 3DS 2.0 challenge, ending in an approved capture |
payment-success-non3ds | Frictionless approval, no 3DS challenge |
payment-declined | Acquirer decline (KVELL-PAY-4001) |
payment-with-installments | Approved charge with installment (taksit) plan validation |
refund | Full refund on a captured payment |
tokenize-and-reuse | Tokenize a card, charge it, then charge the saved token again |
webhook-retry-recovery | First delivery fails, retries, then succeeds β watch the retry ladder |
qr-success | TR QR payment approved via the async bank push |
qr-declined | TR QR payment declined via the async bank push |
payout-success | FAST/EFT/card payout confirmed by the rail provider |
payout-declined | Payout declined by the rail provider |
chargeback-received | A chargeback lands on a captured payment |
Example: forcing a decline
curl -X POST https://api.pay.sandbox.kvell.group/v1/payments/card \
-H "X-Api-Key: pk_demo_a1b2c3d4e5f6g7h8" \
-H "X-Timestamp: 2026-04-13T10:00:00Z" \
-H "X-Signature: <signature>" \
-H "X-Sim-Scenario: payment-declined" \
-H "Content-Type: application/json" \
-d '{
"transactionId": "ORDER-2026-0099",
"amount": 15000,
"currency": "TRY",
"cardToken": "tok_9f8e7d6c5b4a"
}'HTTP/1.1 402 Payment Required{
"code": "KVELL-PAY-4001",
"message": "Payment declined by issuing bank.",
"requestId": "a4e7c1f0-2b3d-4a5e-9f6c-8d1b2e3f4a5c"
}Every error follows the same {code, message, requestId} envelope β branch on code in your code, show message to a human, and log requestId if you need support.
Test card numbers
Sandbox-only, non-working numbers β they never charge a real card.
| Card number | Brand | Behavior |
|---|---|---|
4155 6500 0000 0007 | Visa | Approves (3DS optional) |
4155 6500 0000 0031 | Visa | Declines β insufficient funds (KVELL-PAY-4002) |
4155 6500 0000 0049 | Visa | Declines β stolen card (response code 05) |
5406 6700 0000 0009 | Mastercard | Approves |
9792 0300 0000 0006 | Troy | Approves |
Use any future expiry date and any 3-digit CVC β the sandbox acquirer doesn't validate them. Tokenize these through POST /v1/cards/tokens exactly as you would a real card; only the outcome is scripted, the flow is identical.
Testing 3DS
Send X-Sim-Scenario: payment-success-3ds on the card charge. The response comes back status: "three_d_redirect" with a threeDUrl β follow the same redirect-and-callback flow from Accepting Card Payments, then finish with POST /v1/payments/{id}/3ds-callback. In simulation mode the challenge page auto-submits, so an end-to-end script never blocks on a human clicking through a form.
Testing installments
Set installmentCount on the card charge and use payment-with-installments to confirm your plan validation and commission math line up with what Installment Payments (Taksit) documents.
Testing refunds
Use refund on POST /v1/payments/{id}/refund against a captured payment. Try a partial refund followed by a second partial refund to confirm your integration reads refundableAmount back correctly rather than assuming a payment can only be refunded once.
Testing webhooks
webhook-retry-recovery fails the first delivery attempt on purpose, then lets it succeed on retry β the full 10s β 1m β 10m β 1h β 6h ladder plays out on a compressed sandbox clock, so you can watch your endpoint receive a duplicate delivery and confirm your handler dedupes on X-Kvell-Event-Id instead of assuming exactly-once delivery. Webhooks covers signature verification and the full retry schedule.
Testing idempotency
Kvell exposes idempotency two ways, and both are worth testing:
transactionIdis the built-in fence on payment session and card-charge creation β send a create-session call twice with the sametransactionIdand confirm you get the original session back rather than a duplicate.Idempotency-Keyis an optional header on refunds and payouts β send a refund twice with the same key and body and confirm you get back an identical result with no second reversal. Send the same key with a different body and confirm you get a409instead of a silent overwrite.
The Try-It console
Every endpoint page on this site has a Try-It panel β save your sandbox keys once via the key icon in the header, and every request you send from the console is signed server-side using those keys, so you can explore the API without writing a signing function first.
Next steps
| Guide | What it covers |
|---|---|
| Authentication & Request Signing | Wiring up request signing if you haven't already |
| Going Live | The checklist for moving from sandbox to production |
| Marketplace & Split Payments | Testing split payments and submerchant payouts |