Skip to main content

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.

Boost Your Integration

Ottu offers SDKs and tools to speed up your integration. See Getting Started for all available options.

For business stakeholders

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.

What this page covers

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 extra mechanism 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:

  1. 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.
  2. 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.

MID capabilities default to OFF

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:

Authentication — three accepted modes

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.

Send OPTIONS first

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>'
A MID with no plugins is invisible

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:

Create a session against your Sandbox gateway
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:

MechanismYou control the outcome with…Applies to
Test card / token (synchronous)the test card number the customer enters, or the token you chargeHosted checkout, tokenization, auto-debit (CIT & MIT)
sandbox_inquiry_result (asynchronous)a value in the Checkout extra objectAny 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 extraCapture, 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 NumberExpiryCVV / PINBrandStatusNote
411111111111111AnyAnyVisa (test)SuccessPayment succeeds
411111111111112AnyAnyVisa (test)FailedPayment declined
411111111111113AnyAnyVisa (test)ErrorGateway 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 NumberExpiryCVV / PINBrandStatusNote
411111111111114AnyAnyVisa (test)MIT declineSaves at CIT; declined when charged — issuing bank
411111111111115AnyAnyVisa (test)MIT declineSaves at CIT; declined when charged — card expired
411111111111116AnyAnyVisa (test)MIT declineSaves at CIT; declined when charged — insufficient funds
The Sandbox token is the card number

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 keyValuesApplies toEffect
sandbox_inquiry_resultsuccess · failed · errorAny flow resolved by a status inquiry (unattended / timed-out sessions)The outcome a background or manual inquiry applies. Defaults to success.
sandbox_resultsuccess · failed · errorCapture, void, and wallet paymentsThe 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_minutesintegerPairs with sandbox_inquiry_resultArms an auto-inquiry to apply the outcome after N minutes (non-production only).
POST /b/checkout/v1/pymt-txn/ — pin an unattended session to fail after 5 minutes
{
"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.

Which mechanism wins

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 testHow to trigger it on the SandboxLearn more
Success / failure / errorPay 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 & voidUse 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 shapeOn 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 deliveryPoint 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
Why a decline may show attempted, not failed

Sessions 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:

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

Create an Ottu Sandbox MID

POST 

/b/pbl/v2/osb/create/

Provisions an Ottu Sandbox payment gateway MID with its full configuration, so a test environment can be set up without going through the admin.

What it does

  • Creates (or reuses) the ottu_sandbox gateway row.
  • Resolves the currencies you pass into an exchange configuration, reusing an existing one when it already covers them.
  • Creates the MID itself. The MID is always a sandbox MID - a production MID cannot be minted here.

Required fields

fielddescription
name_enDisplay name shown in the checkout SDK, dashboard and emails.
currency_codeISO 4217 code of the MID's default currency. Must already exist.
tagsOne or more business-vertical tags (e.g. demo, e_commerce).

Notable optional fields

  • code - the identifier used in API calls and URLs. Derived from name_en when omitted; must be unique across every gateway MID.
  • extra_currency_codes - additional currencies the MID accepts.
  • operations - post-payment operations. The sandbox supports refund and void; void requires operation to be authorize.
  • is_tokenizable / auto_debit_enabled - enable card saving and MIT charges.

Rate limit

5 MIDs per hour per caller (per user, or per API key). The 6th call returns 429.

Common responses

  • 201 - the MID was created; the body is its full configuration.
  • 400 - validation error, keyed by field.
  • 401 - missing or invalid credentials.
  • 429 - hourly provisioning limit reached.
curl --location 'https://sandbox.ottu.net/b/pbl/v2/osb/create/' \
--header 'Content-Type: application/json' \
--header 'Accept: application/json' \
--header 'Authorization: Api-Key YOUR_API_KEY' \
--data '{
  "gateway_name": "ottu_sandbox",
  "name_en": "string",
  "name_ar": "",
  "code": "",
  "description": "string",
  "is_active": true,
  "operation": "purchase",
  "operations": [
    "refund"
  ],
  "currency_code": "AED",
  "extra_currency_codes": [
    "AED"
  ],
  "payment_method_type": "bnpl",
  "bank_code": "",
  "plugins": [
    "payment_request"
  ],
  "tags": [
    "e_commerce"
  ],
  "displayable": true,
  "can_have_multiple_attempts": true,
  "is_tokenizable": true,
  "auto_debit_enabled": true,
  "form_fields": {}
}'

Request

Responses

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 same sandbox_inquiry_result value.
  • On the card paths, the card decides. For hosted checkout, tokenization, and auto-debit the result is set by the test card / token, and extra is ignored. sandbox_inquiry_result governs inquiries; sandbox_result governs 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, and operations when 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?