vellTRDocs
GuidesTesting in the Sandbox

Testing in the Sandbox

Beginner10 min

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

SandboxProduction
Base URLhttps://api.pay.sandbox.kvell.grouphttps://api.pay.kvell.group
API keyspk_demo_… / sk_demo_…pk_live_… / sk_live_…
Bank, 3DS, payout railsDeterministic mocksReal acquirer, ACS, FAST/EFT/OCT
X-Sim-Scenario headerHonoredIgnored
Money movementNone β€” simulatedReal

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

ScenarioExercises
payment-success-3dsFull 3DS 2.0 challenge, ending in an approved capture
payment-success-non3dsFrictionless approval, no 3DS challenge
payment-declinedAcquirer decline (KVELL-PAY-4001)
payment-with-installmentsApproved charge with installment (taksit) plan validation
refundFull refund on a captured payment
tokenize-and-reuseTokenize a card, charge it, then charge the saved token again
webhook-retry-recoveryFirst delivery fails, retries, then succeeds β€” watch the retry ladder
qr-successTR QR payment approved via the async bank push
qr-declinedTR QR payment declined via the async bank push
payout-successFAST/EFT/card payout confirmed by the rail provider
payout-declinedPayout declined by the rail provider
chargeback-receivedA chargeback lands on a captured payment

Example: forcing a decline

Shell
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"
  }'
text
HTTP/1.1 402 Payment Required
JSON
{
  "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 numberBrandBehavior
4155 6500 0000 0007VisaApproves (3DS optional)
4155 6500 0000 0031VisaDeclines β€” insufficient funds (KVELL-PAY-4002)
4155 6500 0000 0049VisaDeclines β€” stolen card (response code 05)
5406 6700 0000 0009MastercardApproves
9792 0300 0000 0006TroyApproves

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:

  • transactionId is the built-in fence on payment session and card-charge creation β€” send a create-session call twice with the same transactionId and confirm you get the original session back rather than a duplicate.
  • Idempotency-Key is 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 a 409 instead 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

GuideWhat it covers
Authentication & Request SigningWiring up request signing if you haven't already
Going LiveThe checklist for moving from sandbox to production
Marketplace & Split PaymentsTesting split payments and submerchant payouts