Test it in sandbox
Built-in test rails that return production-shaped payment data, and how to force every outcome.
GET /api/v1/payment-methods from the very first call — you can charge against them out of the box. They only ever appear on a sk_test_ key; a production charge can never reach a test id.Test rails
Charge them exactly like a real rail: pass the paymentMethodId to POST /api/v1/payments. Every charge lands pending with real payment data, and you decide the outcome.
| paymentMethodId | Rail | Country | Currency |
|---|---|---|---|
| sbx_spei | SPEI bank transfer | Mexico | MXN |
| sbx_cash | OXXO Pay (cash voucher) | Mexico | MXN |
| sbx_card_mx | Local credit card | Mexico | MXN |
| sbx_debit_mx | Local debit card | Mexico | MXN |
| sbx_pix | PIX | Brazil | BRL |
| sbx_pse | PSE bank transfer | Colombia | COP |
| sbx_card | Card | United States | USD |
What each rail returns
The payment data comes back in paymentData on the charge response and on GET /payments/{id}. The field names are the SAME ones production uses, so the branch you write here is the branch that runs live.
merchantOrderId always produces the same CLABE, reference and barcode, so you can re-run a test and diff it byte for byte. Two different orders get different data — because that is what production does, and “did I store the CLABE against the right order?” is exactly the bug this catches.// sbx_spei — bank transfer
"paymentData": {
"method": "spei",
"clabe": "646180283352577324", // 18 digits, VALID check digit, one per charge
"bankName": "STP",
"beneficiaryName": "Key2Pay Sandbox",
"reference": "SBX1XBET0001",
"amountLocal": 1820.50, "currencyLocal": "MXN",
"dueDate": "2026-08-27T21:00:00.000Z"
}
// sbx_cash — OXXO voucher
"paymentData": {
"method": "oxxo",
"reference": "SBX1XBET0001",
"barcode": "93022833525773", // what the cashier scans
"digitableLine": "93022833525773",
"amountLocal": 1820.50, "currencyLocal": "MXN",
"dueDate": "2026-08-27T21:00:00.000Z"
}
// sbx_card_mx / sbx_debit_mx — local card
"paymentData": {
"method": "card",
"reference": "SBX1XBET0001",
"amountLocal": 1820.50, "currencyLocal": "MXN",
"completeAt": "hosted_checkout" // send the buyer to checkoutUrl
}
// sbx_pix — PIX
"paymentData": {
"method": "pix",
"reference": "SBX1XBET0001",
"qrCode": "00020126400014br.gov.bcb.pix...6304A1B2", // EMV, real CRC16
"copyPaste": "00020126400014br.gov.bcb.pix...6304A1B2",
"amountLocal": 320.00, "currencyLocal": "BRL",
"dueDate": "2026-08-27T21:00:00.000Z"
}completeAt: "hosted_checkout" and the charge response carries a checkoutUrl: send the buyer there and wait for the webhook. Sandbox behaves exactly like production here — better to find that out now than in certification.Forcing an outcome
A sandbox charge stays pending until you say otherwise. Two ways, same engine behind both — and both fire the REAL signed webhook, so this is how you test your handler.
The simulate endpoint
curl -X POST https://sandbox.key2pay.ai/api/v1/api/v1/payments/TXN-.../simulate -H "Authorization: Bearer sk_test_..." -H "Content-Type: application/json" -d '{"action":"paid"}'| action | status becomes | webhook you receive |
|---|---|---|
| paid | completed | payment.completed |
| failed | failed | payment.failed |
| expired | expired | payment.expired |
| refunded | refunded | payment.refunded |
| chargeback | chargeback | chargeback.created |
Or decide it at charge time
Pass Sandbox-Simulate: paid on the charge itself when you want a one-shot test — useful in CI, where a second round-trip is just noise.
curl -X POST https://sandbox.key2pay.ai/api/v1/api/v1/payments -H "Authorization: Bearer sk_test_..." -H "Sandbox-Simulate: paid" -H "Idempotency-Key: ord_1001" -H "Content-Type: application/json" -d '{"amount":100,"paymentMethodId":"sbx_spei","merchantOrderId":"ord_1001"}'Sandbox vs production
Same endpoints, same field names, same signed webhooks, same error codes. Three things differ, and all three are the point of a sandbox:
- No money moves. The rails are simulated; nothing reaches a bank and no settlement is owed.
- You decide the outcome. In production the provider does, and it can take minutes (SPEI) or days (cash).
- Fees are zero. Your real pricing applies only in production, so amounts here stay clean round numbers.