Ottu Sandbox
The Ottu Sandbox is a built-in, simulated payment gateway. It lets you run every payment scenario end-to-end — success, failure, refunds, tokens, recurring charges, authorizations, captures, voids, and wallet payments (Apple / Google / Samsung Pay) — without touching a live gateway, a real bank, or real money. It gets its own pg_code, you create transactions against it through the standard Checkout API, and the outcome is deterministic: you decide it in advance.
Ottu offers SDKs and tools to speed up your integration. See Getting Started for all available options.
Think of the Ottu Sandbox as a flight simulator for payments. Your team can rehearse a customer paying, a card being declined, a refund being issued, or a subscription renewing — repeatedly and safely — before a single real transaction is processed. It is the fastest way to validate an integration, demo a flow, or reproduce a support issue.
This page documents the Ottu Sandbox simulator gateway specifically. For the catalog of test card numbers used with real gateways' own sandboxes (KNET, MPGS, Checkout.com, etc.), see Sandbox & Test Cards.
When to Use
- Integration testing — validate your Checkout API calls, webhook handling, and order reconciliation before going live.
- Reproducing outcomes on demand — deterministically trigger a success, a decline, or a gateway error to test each of your code paths.
- Demos and UAT — walk a stakeholder through a complete payment, refund, or recurring flow with no financial risk.
- CI / automated tests — drive predictable payment results from an automated test suite (the asynchronous
extramechanism is designed for exactly this). - Recurring / tokenization rehearsals — practice the full CIT → MIT lifecycle (save a card, then auto-debit it) end-to-end.
- Realistic response-shape testing — mimic a real gateway's response payload (KNET, MPGS, CyberSource, …) to validate your webhook parsing and reconciliation without calling the live gateway.
Setup
You need only two things:
- An Ottu Sandbox gateway (MID) configured on your own Connect server. The Sandbox can run on any Ottu environment — dev, UAT, staging, or production — so you can rehearse a specific flow on the very server that flow will run on. You test it exactly the way you'd test KNET or MPGS on your own installation: it gets its own
pg_code, kept separate from your live gateways, and no traffic ever leaves Ottu. The only environment-dependent piece is the automatic inquiry scheduler (expires_in_minutes), which stays off on production-flagged servers — see Best Practices. - A valid API key for the Checkout API. Examples below use
Authorization: Api-Key <YOUR_API_KEY>.
Then pass the Sandbox pg_code in pg_codes when you create a session — that is the entire setup. If you don't have a Sandbox MID yet, you can provision one yourself in a single call — see Provisioning a Sandbox MID below.
Tokenization, auto-debit, and authorize/void are off on a new MID until you turn them on. You can set them at creation time with is_tokenizable, auto_debit_enabled, operation, and operations (see Provisioning a Sandbox MID) instead of clicking through the dashboard. Wallet support is still provisioned by Ottu. A call against a capability the MID doesn't have is rejected at validation rather than simulated.
Provisioning a Sandbox MID
POST /b/pbl/v2/osb/create/ provisions a Sandbox MID with its full configuration in one call, so you can stand up a test environment without going through the admin. The complete request and response schemas — every field, its type, enum values, constraints and defaults — are in the API Reference.
Three things about it are worth knowing before your first call, because none of them are visible in the schema:
This endpoint accepts a private API key (Authorization: Api-Key <private_key>), a logged-in user's JWT, or a Keycloak JWT — both JWTs as Authorization: Bearer <token>. A public API key is rejected with 401, and so is Basic auth. Reaching for the public key first is the most likely mistake. See Authentication.
OPTIONS on the same URL, with the same auth, returns every field with its allowed values under actions.POST — including the currency codes that actually exist on your server. Currencies are per-installation, so this is the only reliable way to know what currency_code will accept.
curl --location --request OPTIONS 'https://sandbox.ottu.net/b/pbl/v2/osb/create/' \
--header 'Authorization: Api-Key <YOUR_PRIVATE_API_KEY>'
plugins is optional, but a MID created without it will not appear anywhere in the product — not in the checkout SDK, not in the dashboard, not in the Payment Methods API. Pass the plugins you actually use, e.g. ["e_commerce", "payment_request"]. Separately, the 5-per-hour provisioning limit is counted per caller (per user or per API key) and only successful creations consume quota — a run of 400s while you get the payload right will not lock you out.
The MID is always a sandbox MID; type is not a request field and a production MID can never be minted here. The code it returns is the pg_code you pass to the Checkout API — omit it and it is derived from name_en and capped at 16 characters.
Guide
Workflow
The single most important concept: with the Ottu Sandbox you choose the result of a payment in advance. Every test starts by creating a session against your Sandbox pg_code:
curl --location 'https://sandbox.ottu.net/b/checkout/v1/pymt-txn/' \
--header 'Authorization: Api-Key <YOUR_API_KEY>' \
--header 'Content-Type: application/json' \
--data-raw '{
"type": "e_commerce",
"pg_codes": ["<your-sandbox-pg_code>"],
"amount": "10.000",
"currency_code": "KWD",
"order_no": "SANDBOX-0001",
"webhook_url": "https://yourwebsite.com/webhook"
}'
From there, there are three independent ways to set the outcome — pick the one that matches how the payment is initiated:
| Mechanism | You control the outcome with… | Applies to |
|---|---|---|
| Test card / token (synchronous) | the test card number the customer enters, or the token you charge | Hosted checkout, tokenization, auto-debit (CIT & MIT) |
sandbox_inquiry_result (asynchronous) | a value in the Checkout extra object | Any flow resolved by a status inquiry (e.g. unattended / timed-out sessions) |
sandbox_result (status override) | a value in the operation's or transaction's extra | Capture, void, and Apple / Google / Samsung Pay — the payment is still made with a test card, but this key decides the resulting status |
Test Cards
On the hosted Sandbox page (and as token values), these cards drive the outcome. They are not real PANs, are not subject to Luhn validation, and never reach a bank.
| Card Number | Expiry | CVV / PIN | Brand | Status | Note |
|---|---|---|---|---|---|
| 411111111111111 | Any | Any | Visa (test) | Success | Payment succeeds |
| 411111111111112 | Any | Any | Visa (test) | Failed | Payment declined |
| 411111111111113 | Any | Any | Visa (test) | Error | Gateway error — 'Simulated error' |
Three further cards enroll and tokenize successfully on the first (CIT) payment but decline when later charged as a saved token (MIT) — purpose-built to rehearse failed recurring charges without any server-side changes:
| Card Number | Expiry | CVV / PIN | Brand | Status | Note |
|---|---|---|---|---|---|
| 411111111111114 | Any | Any | Visa (test) | MIT decline | Saves at CIT; declined when charged — issuing bank |
| 411111111111115 | Any | Any | Visa (test) | MIT decline | Saves at CIT; declined when charged — card expired |
| 411111111111116 | Any | Any | Visa (test) | MIT decline | Saves at CIT; declined when charged — insufficient funds |
When you save a Sandbox card, its token is the card number itself (e.g. 411111111111111). This is what makes recurring testing predictable: charging the token 411111111111111 always succeeds, 411111111111112 always declines. Real gateways issue opaque tokens; the Sandbox keeps it simple on purpose.
The extra object
Some outcomes aren't decided by a card. Background inquiries, captures, and voids have no card in play at all, and a wallet payment (Apple / Google / Samsung Pay) is still made with a Sandbox test card — but its resulting status is taken from extra, not from the card. You set that outcome by adding a key to the Checkout extra object when you create the session (or to an operation request's extra):
extra key | Values | Applies to | Effect |
|---|---|---|---|
sandbox_inquiry_result | success · failed · error | Any flow resolved by a status inquiry (unattended / timed-out sessions) | The outcome a background or manual inquiry applies. Defaults to success. |
sandbox_result | success · failed · error | Capture, void, and wallet payments | The final status of that operation or wallet payment — for wallets it takes precedence over the test card that was used. Defaults to success. |
expires_in_minutes | integer | Pairs with sandbox_inquiry_result | Arms an auto-inquiry to apply the outcome after N minutes (non-production only). |
{
"type": "e_commerce",
"pg_codes": ["<your-sandbox-pg_code>"],
"amount": "10.000",
"currency_code": "KWD",
"webhook_url": "https://yourwebsite.com/webhook",
"extra": {
"sandbox_inquiry_result": "failed",
"expires_in_minutes": 5
}
}
The same sandbox_inquiry_result value is also honored by any manual status inquiry on the transaction (e.g. a Payment Status Query) — expires_in_minutes only controls the automatic scheduling.
On the card paths — hosted checkout, tokenization, and auto-debit — the outcome is decided by the test card / token that is submitted; extra is not consulted there. On a wallet payment the customer still pays with a test card, but the transaction's final status comes from sandbox_result. The two keys never overlap: sandbox_inquiry_result drives inquiries, sandbox_result drives capture, void, and wallet payments. Any unrecognized value (or an absent key) defaults to success.
What You Can Test
The Sandbox exercises the same APIs as production, so anything you build with Ottu you can test here. The reference docs cover each flow in full — this matrix just shows how to trigger it against the Sandbox, so you don't have to re-read the whole guide.
| What you can test | How to trigger it on the Sandbox | Learn more |
|---|---|---|
| Success / failure / error | Pay with 411111111111111 (success), 411111111111112 (failed), or 411111111111113 (error) on the hosted page. For unattended flows, set extra.sandbox_inquiry_result instead of using a card. | Checkout API |
| Save a card (tokenization / CIT) | Create a session with a customer_id and pay with a Success card, ticking Save card. The saved token equals the card number. | Tokenization |
| Recurring / auto-debit (MIT) | Charge a saved token via the Auto-Debit API. Use …114 / …115 / …116 to rehearse a declined recurring charge. | Recurring Payments |
| Refund (full & partial) | Run the refund operation on a paid transaction. Sandbox refunds always succeed; over-refunding is rejected, not clamped. | Operations |
| Authorize, capture & void | Use a MID whose Operation is authorize, then capture or void via Operations. Simulate a declined capture/void with extra.sandbox_result. | Operations |
| Wallet payments (Apple / Google / Samsung Pay) | Complete the wallet sheet with a Sandbox test card — the payment succeeds by default. Set extra.sandbox_result on the session to force a decline or error whichever card was used. | Native Payments |
| Mimic a real gateway's response shape | On the hosted pay page, pick KNET / MPGS / CyberSource / MyFatoorah under "Mimic PG Response". The stored gateway_response takes that gateway's shape while the test card still decides the outcome. | — |
| Failed webhook delivery | Point webhook_url at an endpoint that returns a non-2xx status or times out; the payment still succeeds and the delivery failure is recorded in disclosure_url_error. | Webhooks |
attempted, not failedSessions created via the Checkout API support multiple attempts by default, so a single decline moves the transaction to attempted (the customer can retry) rather than to the terminal failed. It only goes straight to failed/canceled when multi-attempt is disabled on the MID. See Payment States.
API Reference
Payments against the Sandbox use the standard Ottu APIs you already use — there is no Sandbox-specific request format for them. The full interactive schemas live with each API:
- Checkout API — create sessions (
pg_codes: ["<your-sandbox-pg_code>"]) - Auto-Debit API — charge saved tokens (MIT)
- Operations API — refund, capture, and void
The one Sandbox-specific endpoint is the provisioning call. Expand any field for its type, enum values, constraints and defaults, and use the sample request as a starting point.
Create an Ottu Sandbox MID
Best Practices
Get repeatable results from the Sandbox — and know where its simulation stops, so you don't plan production behavior around something it cannot model.
- Any environment works — with one exception. A Sandbox MID can be configured on any Ottu server, production included, so you can rehearse a flow where it actually runs. The only environment-dependent piece is the automatic inquiry scheduler (
sandbox_inquiry_result+expires_in_minutes): on a production-flagged server those sessions never auto-resolve, so trigger the inquiry yourself with a Payment Status Query — it honors the samesandbox_inquiry_resultvalue. - On the card paths, the card decides. For hosted checkout, tokenization, and auto-debit the result is set by the test card / token, and
extrais ignored.sandbox_inquiry_resultgoverns inquiries;sandbox_resultgoverns capture, void, and wallet payments — including the wallet case, where a test card is submitted but does not decide the status. - Enable the capability you're testing. MID capabilities default to off. Set
is_tokenizable,auto_debit_enabled,operation, andoperationswhen you create the MID — it is one call, and it beats discovering the gap halfway through a test run. Wallets are provisioned by Ottu. A call against a capability the MID doesn't have is rejected at validation, not simulated. - Don't rely on the Sandbox for declined refunds or 3DS. Refund always succeeds — it cannot simulate a declined or rejected refund (capture, void, and wallets can be failed via
sandbox_result). There is also no 3-D Secure challenge or redirect. Test those flows against a real gateway's sandbox.
FAQ
What's Next?
- Checkout API — Create the sessions every test starts from
- Recurring Payments — The full CIT → MIT token lifecycle
- Operations — Refund, capture, and void in depth
- Payment States — How outcomes map to transaction states
- Webhooks — Receive and verify payment results
- Sandbox & Test Cards — Test cards for real gateways' sandboxes