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. You configure it like any other gateway (it gets its own pg_code), 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 three things before running any scenario:

  1. An Ottu Sandbox gateway (MID) configured on a non-production environment (e.g. your *.ottu.dev / sandbox subdomain). It is set up in the Ottu Dashboard like any other gateway. The Sandbox is intended for dev/UAT servers — see Best Practices.
  2. The MID's pg_code — pass it in pg_codes when creating a session, exactly as you would for any real gateway.
  3. A valid API key for the Checkout API. Examples below use Authorization: Api-Key <YOUR_API_KEY>.

MID configuration prerequisites

A freshly created Ottu Sandbox MID has the advanced capabilities switched off by default. Enable the ones your scenario needs in the Ottu Dashboard (Payment Gateway settings) — otherwise the corresponding API calls are rejected at validation:

Capability you want to testMID setting to enableDefault
Tokenization / save cardTokenizable (is_tokenizable)Off
Auto-debit (recurring / MIT)Auto debit enabled (auto_debit_enabled)Off
Refundadd refund to the MID's OperationsOff (empty)
Authorization & captureset Operation to authorizepurchase
Voidadd void to the MID's OperationsOff (empty)
Apple / Google / Samsung Payadd the wallet's service configuration to the MIDOff
If you skip this, "nothing works"

With a vanilla Sandbox MID, refund returns "refund operation is not enabled for this MID", auto-debit is rejected, and authorize never happens (every payment settles as paid). Always enable the capability for the scenario you are testing first.

Checklist

  • Ottu Sandbox MID created on a non-production environment.
  • Captured the MID's pg_code.
  • Enabled is_tokenizable (for saved-card / auto-debit scenarios).
  • Enabled auto_debit_enabled (for MIT scenarios).
  • Added refund to Operations (for refund scenarios).
  • Set Operation to authorize (for the authorize / capture scenario).
  • Added void to Operations (for the void scenario).
  • Configured the relevant wallet service (for Apple / Google / Samsung Pay scenarios).
  • A webhook_url endpoint ready to receive results (recommended).

Test Cards

On the hosted Sandbox page (and as token values), these cards drive the outcome. They are not real PANs and never reach a bank.

These are 15-digit test identifiers

The Sandbox cards are 15 digits (e.g. 411111111111111) — one digit shorter than a standard 16-digit Visa PAN. They are fixed test identifiers, not real card numbers and not subject to Luhn validation, so the length is expected. Each number maps to a fixed outcome, below.

Card numberOutcomePayment attempt result
411111111111111SuccessPayment succeeds
411111111111112FailedPayment declined
411111111111113ErrorGateway error (message: "Simulated error")
any other valueRejectedTreated as an unknown card (canceled)

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 server-side changes (see Scenario 5):

Card numberFirst payment (CIT)Saved-token charge (MIT)Decline reason surfaced
411111111111114succeeds, card saveddeclinedTransaction declined by the issuing bank.
411111111111115succeeds, card saveddeclinedCard expired.
411111111111116succeeds, card saveddeclinedInsufficient 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.

Guide

Workflow

The single most important concept: with the Ottu Sandbox you choose the result of a payment in advance. There are three independent ways to do that — pick the one that matches how the payment is initiated.

MechanismYou control the outcome with…Applies toWhen the outcome is applied
A — Card / token driven (synchronous)the test card number the customer enters, or the token you chargeHosted checkout, tokenization, auto-debit (CIT & MIT)Immediately, at the moment of payment
B — sandbox_inquiry_result (asynchronous)the sandbox_inquiry_result value in the Checkout extra objectAny flow resolved by a status inquiry (e.g. unattended / timed-out sessions)Later, by a background inquiry — no customer interaction needed
C — sandbox_result (card-less surfaces)a sandbox_result value (success · failed · error) in the operation request's extra (capture / void) or the transaction's extra (wallets)Capture, void, and Apple / Google / Samsung Pay — surfaces with no card to pickAt the moment the operation or wallet payment runs
extra is consulted only on the non-interactive paths

The Sandbox is often described as "the merchant sets the outcome in extra." That is precise for Mechanism B (the asynchronous inquiry path, sandbox_inquiry_result) and Mechanism C (capture / void / wallets, sandbox_result). For interactive checkout, tokenization, and auto-debit (Mechanism A), the outcome is decided by the test card number / token that is submitted — extra is not consulted on those paths. The two keys are distinct and 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.

Each scenario below lists its prerequisites, the exact request, and the expected result. All examples are copy-pasteable — replace the API key, pg_code, and session_id with your own.

Scenario 1 — Hosted Checkout (full redirect flow)

The customer is redirected to Ottu's hosted Sandbox page, picks a test card, and the result flows back to you via webhook — exactly like a real hosted gateway.

Prerequisites: a Sandbox MID (pg_code). For UX testing, a webhook_url to receive the result.

Step 1 — Create the session (Checkout API):

Create a hosted-checkout session against the Sandbox PG
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": ["ottu-sandbox"],
"amount": "10.000",
"currency_code": "KWD",
"order_no": "SANDBOX-0001",
"webhook_url": "https://yourwebsite.com/webhook"
}'

Response (abridged):

Checkout response
{
"session_id": "037ad20c32615e7bc2f9620fd0aec912423e06c4",
"checkout_url": "https://<your-subdomain>.ottu.dev/b/checkout/redirect/start/?session_id=037ad20c...",
"state": "created"
}

Step 2 — Send the customer to checkout_url. They land on the hosted Ottu Sandbox page, which shows the amount, transaction details, and the three test cards. The customer clicks a test card (or types its number) and submits.

Step 3 — Outcome. The submitted card decides the result, the transaction transitions accordingly, and (if you supplied a webhook_url) Ottu posts a payment notification to your server:

Card enteredPayment attemptTransaction state
411111111111111successpaid
411111111111112failedattempted → retryable, or failed when retries are disabled
411111111111113error ("Simulated error")attempted → retryable, or failed when retries are disabled
any other valuecanceled (unknown card)attempted, or canceled when retries are disabled
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.

Scenario 2 — Mimic a Real Gateway's Response Shape

By default the Sandbox stores a minimal stub ({"card_number": "…"}) as the gateway response. When you need your integration to handle a real gateway's response shape — the exact JSON that KNET, MPGS, CyberSource, or MyFatoorah returns — the hosted pay page can mimic it. This lets you exercise your webhook parsing, reconciliation, and gateway-specific handling against production-shaped payloads without ever calling the live gateway.

What "mimic" changes — and what it does not

The mimic selector only changes the shape of the stored gateway_response. It does not change the result: the test card still decides success / failed / error. Picking KNET + the Failed card produces a KNET-shaped decline; KNET + the Success card produces a KNET-shaped capture. The payload is realistic (with freshly randomized identifiers each run) and is stored verbatim — Ottu does not re-interpret it, so the transaction state always matches the card.

Applies to: the hosted checkout pay page only (Scenario 1). It is a manual QA aid — the asynchronous extra path (Scenario 3) and token charges (Scenario 5) do not use it.

Prerequisites: same as Scenario 1 — a Sandbox MID (pg_code) and, to see the result delivered, a webhook_url.

Steps:

  1. Create a hosted-checkout session exactly as in Scenario 1 and send the customer to checkout_url.
  2. On the Sandbox pay page, under "Mimic PG Response" (optional), click the logo of the gateway you want to imitate — KNET, MPGS, CyberSource, or MyFatoorah. (Click the active logo again to clear the selection and fall back to the default stub.)
  3. Click a test card to choose the outcome, then press Pay.

Result. The transaction settles per the card, and the stored response — surfaced in the gateway_response field of your payment webhook (and in the dashboard transaction details) — matches the chosen gateway's real shape:

KNET + the Success card → a CAPTURED response, the same shape your KNET integration parses:

webhook → gateway_response (KNET)
{
"result": "CAPTURED",
"auth": "B65880",
"ref": "461580512345",
"authRespCode": "00",
"avr": "N",
"paymentid": "100202412345678901",
"tranid": "987654321012345",
"trackid": "<your reference_number>",
"postdate": "0623",
"amt": "10.000"
}
Identifiers are randomized; outcome fields are fixed

Every mimic run generates fresh identifiers (paymentid, tranid, order.id, transaction_id, …) so repeated tests never collide. Only the shape and the outcome-specific fields (result, gatewayCode, decision, …) are deterministic; amounts and currency echo your session. MyFatoorah follows the same principle in its own response shape.

When to use mimic vs. the default stub

Use mimic when you are validating code that reads the raw gateway response — reconciliation jobs, response-shape assertions, or gateway-specific webhook handling. For ordinary outcome testing (paid / failed / error) you don't need it; the default stub is enough.

Scenario 3 — Predefined Outcome via extra (asynchronous)

This is Mechanism B: pin the outcome at creation time and let a background inquiry apply it later — no customer, no pay page. Ideal for automated tests and unattended flows.

The Sandbox reads two keys from the Checkout extra object:

extra keyValuesMeaning
sandbox_inquiry_resultsuccess · failed · errorThe outcome the inquiry will apply. Defaults to success when omitted or invalid.
expires_in_minutesinteger (minutes)Arms the auto-inquiry: schedules the outcome to be applied after this delay.

Prerequisites: a Sandbox MID on a non-production environment (the auto-scheduler is disabled in production — see Best Practices).

Request — create the session with both keys in extra:

POST /b/checkout/v1/pymt-txn/ — body
{
"type": "e_commerce",
"pg_codes": ["ottu-sandbox"],
"amount": "10.000",
"currency_code": "KWD",
"webhook_url": "https://yourwebsite.com/webhook",
"extra": {
"sandbox_inquiry_result": "success",
"expires_in_minutes": 5
}
}

After ~5 minutes a background inquiry settles the transaction to paid and your webhook fires.

How it works

On a non-production server, creating a session that carries both sandbox_inquiry_result and expires_in_minutes schedules a one-time background job. When it runs, it asks the Sandbox for the transaction status, and the Sandbox returns whatever you put in sandbox_inquiry_result. If the customer already completed the same session interactively (Scenario 1) before the timer fires, the scheduled job is a no-op — the real, card-driven result stands.

sandbox_inquiry_result also applies to manual inquiries

The same extra value is honored by any status inquiry on the transaction — for example a Payment Status Query — not just the scheduled one. The expires_in_minutes key only controls the automatic scheduling.

Scenario 4 — Tokenization & Saving a Card (CIT)

A CIT (Cardholder-Initiated Transaction) is the first, customer-present payment that saves a card for future use. On the Sandbox this is a hosted checkout where the customer ticks "Save card" (or where the transaction is auto_debit, which saves the card automatically).

Prerequisites: MID with is_tokenizable enabled; the session must include a customer_id.

Request — create a session with customer_id and (for recurring) payment_type: auto_debit + an agreement:

Create a CIT session that saves a card
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",
"payment_type": "auto_debit",
"pg_codes": ["ottu-sandbox"],
"amount": "10.000",
"currency_code": "KWD",
"customer_id": "cust-123",
"webhook_url": "https://yourwebsite.com/webhook",
"agreement": {
"id": "agr-001",
"type": "recurring",
"amount_variability": "fixed",
"frequency": "monthly"
}
}'

Outcome. Send the customer to checkout_url, have them pay with the Success card 411111111111111 and tick Save card. The transaction becomes paid, a saved card is created for cust-123, and the webhook delivers the token:

Webhook (token delivered)
{
"session_id": "4a462681df6aab64e27cedc9bbf733cd6442578b",
"result": "success",
"state": "paid",
"payment_type": "auto_debit",
"customer_id": "cust-123",
"agreement": { "id": "agr-001", "type": "recurring" },
"token": {
"token": "411111111111111",
"customer_id": "cust-123",
"brand": "VISA",
"number": "**** 1111",
"pg_code": "ottu-sandbox",
"agreements": ["agr-001"]
}
}
The outcome is set by the card, not by extra

For tokenization the result is decided by the card used: 411111111111111 saves successfully, 411111111111112 declines, 411111111111113 errors. Save token.token and token.pg_code — you will use them in Scenario 5. See Recurring Payments for the full token lifecycle.

Scenario 5 — Auto-Debit with a Saved Token (MIT)

A MIT (Merchant-Initiated Transaction) charges a previously saved token with the customer absent — the basis of subscriptions and recurring billing.

Prerequisites:

  • MID with auto_debit_enabled enabled.
  • A prior successful CIT for the same customer_id, on the same MID, under the same agreement.id (Scenario 4). The Sandbox enforces this — an MIT without a matching CIT is rejected with "No Cardholder Initiated Transaction (CIT) found."
  • A new auto_debit session for the same customer/agreement, and its session_id.

You can charge the token two ways:

Create a new auto_debit session, then charge the saved token:

Charge a saved token (MIT)
curl --location 'https://sandbox.ottu.net/b/pbl/v2/auto-debit/' \
--header 'Authorization: Api-Key <YOUR_API_KEY>' \
--header 'Content-Type: application/json' \
--header 'Idempotency-Key: <unique-uuid>' \
--data-raw '{
"session_id": "<new_auto_debit_session_id>",
"token": "411111111111111"
}'

Outcome. The result is decided by the token charged, and is returned directly in the API response (MIT charges do not produce a separate webhook):

Saved token chargedResultHTTP
411111111111111success → transaction paid200
411111111111114failedTransaction declined by the issuing bank.400
411111111111115failedCard expired.400
411111111111116failedInsufficient funds.400
any other tokenerror ("Simulated error")400

Only cards that enroll successfully at CIT can become saved tokens — the Success card and the three MIT-failure cards. (The …112/…113 cards fail at CIT, so they can never be saved.)

On success the response is the full payment object (the same shape as a payment webhook); a decline returns an error envelope:

Auto-debit response — success (HTTP 200, abridged)
{
"session_id": "4a462681df6aab64e27cedc9bbf733cd6442578b",
"result": "success",
"state": "paid",
"amount": "10.000",
"currency_code": "KWD",
"payment_type": "auto_debit",
"customer_id": "cust-123",
"agreement": { "id": "agr-001", "type": "recurring" },
"gateway_response": {}
}
Auto-debit response — decline (HTTP 400)
{
"detail": "Card expired.",
"result": "failed"
}
Simulating a failed recurring charge

Run the CIT (Scenario 4) with one of the MIT-failure cards (411111111111114 / 411111111111115 / 411111111111116) — they enroll and save exactly like the Success card — then charge the resulting saved token. The MIT then declines with the matching reason (issuer decline / expired card / insufficient funds), so you can exercise your dunning and retry logic end-to-end.

Idempotency

The Auto-Debit API honors the Idempotency-Key header — retrying with the same key returns the original result instead of double-charging. See the API Reference.

Scenario 6 — Refund (full & partial)

Refund a settled Sandbox payment through the unified Operations API.

Prerequisites: a transaction in paid state on a MID that has refund enabled in its Operations.

Omit amount to refund the full remaining balance:

POST /b/pbl/v2/operation/ — body
{
"operation": "refund",
"session_id": "<paid_session_id>"
}

Outcome. Every eligible refund on the Sandbox succeeds and creates a child transaction in the refunded state for the refunded amount. The parent's refunded balance is updated.

Sandbox refund rules
  • Over-refunding is rejected, not clamped: "The amount specified is greater than the amount available for refund."
  • Multiple partial refunds are allowed, cumulatively bounded by the original amount.
  • The Sandbox refund always succeeds — it cannot simulate a declined/rejected refund.
  • Include a Tracking-Key header to make refunds idempotent.

Scenario 7 — Authorize, Capture & Void

An authorization reserves funds without collecting them; a capture settles them later; a void releases the hold without charging. The Sandbox simulates the full lifecycle, including failed captures and voids.

Prerequisites:

  • A Sandbox MID with Operation = authorize — required for both the authorization and the capture.
  • To void, also add void to the MID's Operations.

Step 1 — Authorize

Create and pay a session as in Scenario 1 with the Success card 411111111111111. Instead of paid, the transaction settles in the authorized state — funds reserved, awaiting capture or void.

Webhook / status
{
"session_id": "9a1c...",
"result": "success",
"state": "authorized"
}

Step 2 — Capture

Capture settles an authorized transaction through the Operations API. Omit amount to capture the full authorized amount, or pass a smaller amount for a partial capture (repeatable until the authorized amount is exhausted).

POST /b/pbl/v2/operation/ — body
{
"operation": "capture",
"session_id": "<authorized_session_id>"
}

A successful capture creates a child transaction in the paid state for the captured amount and returns a Processed amount - <amount> message; the parent's captured balance is updated.

Simulating a failed capture. Add sandbox_result to the operation's extra (Mechanism C). On failed/error the capture is rejected, no child transaction is created, and the parent stays authorized:

POST /b/pbl/v2/operation/ — simulate a declined capture
{
"operation": "capture",
"session_id": "<authorized_session_id>",
"extra": { "sandbox_result": "failed" }
}
extra.sandbox_resultCapture resultParent state after
omitted / successchild paid createdpaid (full) or still authorized (partial)
failedrejected — "Simulated decline"stays authorized
errorrejected — "Simulated error"stays authorized

Step 3 — Void

Void releases an authorization in full (the amount field is ignored). It requires void in the MID's Operations and is only available before any capture.

POST /b/pbl/v2/operation/ — body
{
"operation": "void",
"session_id": "<authorized_session_id>"
}

A successful void creates a child transaction in the voided state for the full authorized amount. As with capture, extra.sandbox_result: "failed" (or "error") simulates a rejected void that leaves the transaction authorized.

Capture or void — not both

A transaction can be either captured or voided. Void is rejected once any capture child exists ("This transaction is not eligible for void."), and capture is rejected once the transaction is voided. Both operations also require the MID's default currency to match the transaction currency.

Scenario 8 — Testing a Failed Webhook Delivery

The ticket lists "failed to post webhook" as an outcome to validate. To set expectations clearly: this is not a Sandbox-controlled outcome (there is no extra switch for it). Webhook delivery is a platform-wide behavior, and the Sandbox is simply a convenient, free way to trigger it.

How to reproduce it:

  1. Create a session whose webhook_url points at an endpoint that fails — unreachable host, or one that returns a non-2xx status, or one that times out.
  2. Pay with the Success card 411111111111111 so a result exists to disclose, which makes Ottu attempt the webhook.

What you will observe:

  • The payment itself still succeeds (paid) — webhook delivery is independent of the payment result.
  • The failure is recorded on the payment attempt in disclosure_url_error — the field to check to detect a failed delivery.
  • Retries happen only on timeouts (governed by your Webhook settings); a non-2xx or connection error is not retried.
  • If enabled, Ottu emails a webhook-error alert to the configured address.
No webhook_url, no webhook

If you don't supply a webhook_url on the Checkout request, no payment webhook is sent at all — Ottu just redirects the customer to the result page. Always set webhook_url when testing delivery.

Scenario 9 — Wallet Payments (Apple Pay, Google Pay & Samsung Pay)

The Sandbox simulates all three device wallets. They are initiated the usual way (via the Ottu SDK / native wallet sheet); the Sandbox ignores the wallet payload contents and decides the outcome from the transaction's extra.

Prerequisites: the wallet you want to test must be enabled on the Sandbox MID — add the corresponding Apple Pay / Google Pay / Samsung Pay service configuration in the Dashboard, exactly as you would for a real gateway. (The Sandbox already wires the handlers for all three; the service configuration is what makes a wallet appear as a payment option.)

Default outcome. Every wallet payment succeeds — the transaction becomes paid (or authorized if the MID Operation is authorize).

Simulating failure / error. Wallet payments carry no card and no per-call extra, so the outcome is read from the transaction's extra (Mechanism C) — set it when you create the session:

POST /b/checkout/v1/pymt-txn/ — body (simulate a declined wallet payment)
{
"type": "e_commerce",
"pg_codes": ["ottu-sandbox"],
"amount": "10.000",
"currency_code": "KWD",
"webhook_url": "https://yourwebsite.com/webhook",
"extra": { "sandbox_result": "failed" }
}
extra.sandbox_resultWallet resultFinal state
omitted / successpayment succeedspaid (or authorized)
faileddeclined — "Simulated decline"attempted / failed
errorgateway error — "Simulated error"attempted / failed
All three wallets behave identically

Apple Pay, Google Pay and Samsung Pay share the same simulator logic — only the configured service differs. The same sandbox_result value drives whichever wallet the customer uses, and it is read from the transaction's extra, not from the wallet payload.

Outcome Reference

A consolidated map of every Sandbox trigger and its result.

SurfaceTriggerResultFinal state
Hosted pay pagecard 411111111111111successpaid (or authorized if Operation = authorize)
Hosted pay pagecard 411111111111112failedattempted / failed
Hosted pay pagecard 411111111111113error (Simulated error)attempted / failed
Hosted pay pageany other cardrejected (unknown card)attempted / canceled
Tokenization (save card)card 411111111111111 or …114/115/116successcard saved
Tokenization (save card)card 411111111111112 / …113failed / errornot saved
Auto-debit / token chargetoken 411111111111111successpaid
Auto-debit / token chargetoken …114 / …115 / …116failed (issuer decline / expired / insufficient)400
Auto-debit / token chargeany other tokenerror (Simulated error)400
Inquiryextra.sandbox_inquiry_result: success (default)successpaid
Inquiryextra.sandbox_inquiry_result: failedfailedfailed
Inquiryextra.sandbox_inquiry_result: errorerrorerror
Capturedefault / extra.sandbox_result: successsuccesschild paid
Captureextra.sandbox_result: failed / errorrejected (Simulated decline / error)stays authorized
Voiddefault / extra.sandbox_result: successsuccesschild voided
Voidextra.sandbox_result: failed / errorrejected (Simulated decline / error)stays authorized
Apple / Google / Samsung Paydefault / extra.sandbox_result: successsuccesspaid (or authorized)
Apple / Google / Samsung Payextra.sandbox_result: failed / errordeclined / errorattempted / failed
Refundany eligible refundalways successchild refunded

API Reference

The Sandbox uses the same APIs as any gateway. The interactive schemas:

Create a new Payment Transaction

Create a new Payment Transaction

POST 

/b/checkout/v1/pymt-txn/

Create a new Payment Transaction

Permissions

Auth MethodRequired Permissions
API KeyAll permissions (admin access)
Basic AuthCan add payment requests (Payment Request) or Can add e-commerce payments (E-Commerce)

Gateway permission: Can use pg_code for the target payment gateway

info

The PUT operation cannot be used if the user does not have permission to use the previously defined payment gateway code on the transaction.

curl --location 'https://sandbox.ottu.net/b/checkout/v1/pymt-txn/' \
--header 'Content-Type: application/json' \
--header 'Accept: application/json' \
--header 'Authorization: Api-Key YOUR_API_KEY' \
--data '{
  "type": "e_commerce",
  "amount": "10.000",
  "currency_code": "KWD",
  "pg_codes": [
    "knet"
  ]
}'

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.

  • Enable a capability before you test it. A fresh Sandbox MID ships with tokenization, auto-debit, refund, authorize, void, and wallets switched off. Turn on the one your scenario needs first (see Setup) — otherwise the call is rejected at validation, not simulated.
  • Keep extra-driven async tests on non-production. The sandbox_inquiry_result + expires_in_minutes scheduler runs on dev/UAT servers only. On a production-flagged server those sessions never auto-resolve — use a *.ottu.dev / sandbox subdomain.
  • Drive interactive outcomes with the card, not extra. For hosted checkout, tokenization, and auto-debit the result is set by the test card / token. extra (sandbox_inquiry_result, sandbox_result) only governs the inquiry, capture, void, and wallet paths — it is ignored on the interactive card paths.
  • 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; the outcome is decided directly by the card / token / sandbox_result. Test those flows against a real gateway's sandbox.
  • Treat mimic as a response-shape tool only. Selecting a gateway under Mimic PG Response changes the stored payload's shape, never the outcome — the card still decides — and the payload is stored verbatim, not re-processed.

FAQ

What's Next?