> ## Documentation Index
> Fetch the complete documentation index at: https://docs.kori.ml/llms.txt
> Use this file to discover all available pages before exploring further.

# Sandbox

> Test the full payment lifecycle without moving money — outcomes chosen by phone number.

The sandbox is a separate deployment with its own database and its own credentials.
Nothing you do there touches live money, live merchants or live balances.

```bash Sandbox theme={null}
https://sandbox.kori.ml
```

Sign up at `sandbox.kori.ml` to get sandbox API keys. They are not interchangeable with
production keys, and neither environment can see the other's data.

## It is not a mock

A sandbox payment travels the same path as a real one. The mobile-money operator is
simulated; everything after it is the production code. The status endpoint, settlement,
your balance, the `payment_received` webhook with a genuine signature, the reconciler and
the 24-hour expiry all behave exactly as they will in production.

That includes the timing: **a payment does not settle instantly.** It stays `pending` for
a few seconds first, so code that assumes an immediate answer fails here rather than in
production.

## Choosing an outcome

The outcome is decided by the **customer's phone number**. Your request body is
byte-identical to the one you will send in production — there is no test-only field to
remember to remove.

| Phone number     | Outcome                      | What it exercises                                                |
| ---------------- | ---------------------------- | ---------------------------------------------------------------- |
| `+22370000000`   | Succeeds after a few seconds | The happy path: settlement, credit, `payment_received`           |
| `+22370000001`   | Fails                        | A refusal by the operator, or a customer declining               |
| `+22370000002`   | Never confirms               | Stays `pending`, then `expired` after 24h — an abandoned payment |
| `+22370000003`   | The operator never answers   | The timeout path, which is **not** a failure                     |
| Any other number | Succeeds                     | Ordinary testing                                                 |

```bash theme={null}
curl -X POST https://sandbox.kori.ml/merchant/api/pay \
  -H "Authorization: Bearer <sandbox token>" \
  -H "Content-Type: application/json" \
  -d '{
    "amount": 5000,
    "payment_method": "OrangeMoney",
    "customer_phone": "+22370000001",
    "reference": "test_1024"
  }'
```

That returns `202` with `status: "pending"`, exactly like production. A few seconds later
the payment resolves as `failed`, and if you registered a webhook you receive the event.

<Tip>
  `GET /merchant/api/sandbox/test-numbers` returns this table as JSON, including the
  current settle delay.
</Tip>

## What to test with each

Reach for the special numbers when you want to prove your integration survives the paths
that are hard to reproduce in production:

* **`...0002` (never confirms)** — does your order sit unresolved rather than being marked
  failed? This is the most common integration bug.
* **`...0003` (timeout)** — do you retry rather than treating it as a decline? A timeout
  means the answer is unknown, and the payment may still complete.
* **`...0001` (fails)** — do you let the customer try again, and do you avoid
  double-charging when they do?

## Wave's hosted checkout

Wave payments return a `payment_url` in production, where the customer approves on Wave's
own page. In the sandbox that URL points at a stand-in page that plays back the same
redirect, so the round trip back to your `return_url` (with `?ref=`) is testable. The
outcome is still decided by the phone number — approving on the page changes nothing.

## Payouts

Sandbox merchants are funded with a test balance, so you can create payouts immediately.
The same numbers apply: pay out to `+22370000001` to see a failure refund the balance.

<Note>
  Minimums are enforced in the sandbox exactly as in production: **100 XOF** to collect,
  **1 000 XOF** to send. See [Introduction](/en/introduction).
</Note>
