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

# Testing in Sandbox

> The complete reference for building and testing your integration against the Afriex sandbox: test values, deterministic outcomes, virtual account transfers, OTP flow, balance topups, and webhook drills.

Every Afriex environment behaves the same way on the wire: your requests, responses, webhooks and error codes are identical between sandbox and production. Sandbox just refuses to move real money.

<Info>
  **Base URL:** `https://sandbox.api.afriex.com`, with a sandbox API key.
  Deposits, payouts, and virtual accounts can all be tested on **every payment method and currency** your business supports.
</Info>

<Card title="See the hosted checkout in 30 seconds" icon="play" href="https://sandbox.pay.afriex.com/demo">
  Try the sandbox demo, no API key or code required. Pick a currency and amount, click **Proceed to payment**, and walk through mobile money or bank transfer as your customers would. The demo is sandbox-only and uses the real hosted checkout page under the hood.
</Card>

## When a transaction settles

A sandbox deposit or payout completes on its own about **5 minutes** after it is created, and your webhook receives the final status exactly as in production. When you need it sooner:

* Include `SIMULATE_INSTANT` anywhere in `meta.reference` when you create the transaction, and it completes in about **30 seconds**.
* Or call [Simulate Transaction Outcome](/api-reference/endpoint/transactions/simulate) with `{"outcome": "success"}` or `{"outcome": "failed"}` to finalize it immediately.

Either way the `TRANSACTION.UPDATED` webhook is sent when the transaction settles.

## Choosing the outcome with the last 4 digits

The last four digits of the **account number**, the **phone number**, or the **card number** decide what happens. Everything else about the account is up to you, so you can build one payment method per outcome and reuse it across tests. A payment method with none of these to key off (an email or a wallet address, for example) simply succeeds unless you complete it with [`/simulate`](#simulating-a-pending-transaction).

| Ends in | Payout (`WITHDRAW`) | Mobile money deposit (`DEPOSIT`) | Card deposit (`DEPOSIT`, via [checkout](/api-reference/endpoint/checkout-sessions/create)) |
| - | - | - | - |
| anything else | Succeeds | Succeeds | Succeeds |
| `0001` | Fails; your balance is refunded | Fails | Fails |
| `0002` | Succeeds | Waits for a one-time password (see [OTP](#otp-required-mobile-money-deposits)) | Succeeds |
| `0003` | Stays pending until you call [`/simulate`](#simulating-a-pending-transaction) | Stays pending until you call [`/simulate`](#simulating-a-pending-transaction) | Stays pending until you call [`/simulate`](#simulating-a-pending-transaction) |
| `0004` | Account name lookup ([`/resolve`](/api-reference/endpoint/payment-methods/resolve)) fails | Succeeds | Succeeds |
| `0005` | Declined immediately | Declined immediately | Declined immediately |

<Note>
  **Cards are hosted-checkout only.** The "Card deposit" column above applies to card payments made through a [checkout session](/api-reference/endpoint/checkout-sessions/create); [Create Payment Method](/api-reference/endpoint/payment-methods/create) does not accept `CARD` as a channel.
</Note>

<Note>
  A phone number must still be a valid mobile number for its country, so keep the country prefix and change only the last digits. For example `+254712340002` in Kenya. Account name lookups against a sandbox account return **`Sandbox Test Account`**.
</Note>

## Simulating a pending transaction

Any payment method ending in `0003` puts the transaction in `PENDING` and holds it there until you tell the sandbox what to do. Call [Simulate Transaction Outcome](/api-reference/endpoint/transactions/simulate):

```bash theme={null}
curl -X POST \
  -H "x-api-key: YOUR_SANDBOX_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"outcome": "success"}' \
  https://sandbox.api.afriex.com/api/v1/transaction/TRANSACTION_ID/simulate
```

Pass `{"outcome": "failed"}` to test your failure path. The result arrives by the `TRANSACTION.UPDATED` webhook shortly after this call returns.

You can use the same endpoint to complete **any** pending sandbox transaction early, not just ones ending in `0003`. The transaction must still be `PENDING`, `PROCESSING`, or `UNKNOWN`; a deposit waiting on OTP (`CUSTOMER_ACTION_REQUIRED`) has to be authorized first.

## OTP-required mobile money deposits

A mobile-money deposit from a wallet ending in `0002` returns `CUSTOMER_ACTION_REQUIRED`. Complete it with [Authorize Transaction](/api-reference/endpoint/transactions/authorize) using the sandbox OTP **`123456`**:

```bash theme={null}
curl -X POST \
  -H "x-api-key: YOUR_SANDBOX_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"type":"OTP","otp":"123456"}' \
  https://sandbox.api.afriex.com/api/v1/transaction/TRANSACTION_ID/authorize
```

Any other OTP returns `OTP_INCORRECT`, so you can test the wrong-OTP path too. After authorization the deposit completes on the normal sandbox timeline (or you can accelerate it via [`/simulate`](#simulating-a-pending-transaction)).

## Simulating an incoming bank transfer

Sandbox virtual accounts receive no money on their own. Pay into one with [Simulate Virtual Account Transfer](/api-reference/endpoint/payment-methods/virtual-account-simulate-transfer):

```bash theme={null}
# Reusable virtual account (created without an amount)
curl -X POST \
  -H "x-api-key: YOUR_SANDBOX_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"accountNumber": "4821930275", "amount": 5000, "currency": "NGN"}' \
  https://sandbox.api.afriex.com/api/v1/payment-method/virtual-account/simulate-transfer
```

For a **one-time** virtual account (created with an `amount`), also pass the `reference` the account was issued with. Pass `"outcome": "failed"` to simulate a failed inflow. The credit lands as a transaction and is reported by webhook.

## Testing cards

Cards are only collected through a hosted [checkout session](/api-reference/endpoint/checkout-sessions/create), never through [Create Payment Method](/api-reference/endpoint/payment-methods/create). To test a card deposit:

<Steps>
  <Step title="Create a checkout session">
    Call [Create Checkout Session](/api-reference/endpoint/checkout-sessions/create) with `channels` including `"CARD"`. Redirect the payer to the returned `checkoutUrl`.
  </Step>

  <Step title="Enter a test card">
    On the hosted page, use a test card whose **last four digits** pick the outcome from the [table above](#choosing-the-outcome-with-the-last-4-digits) (`0001` fails, `0003` stays pending, and so on). The hosted page pre-fills a valid test card in read-only form on sandbox, so you can click **Pay** without typing anything.
  </Step>

  <Step title="Observe the transaction">
    The card deposit lands as a `DEPOSIT` transaction and settles on the sandbox timeline described above. Use [`/simulate`](#simulating-a-pending-transaction) to finalize a `0003` pending card deposit on demand.
  </Step>
</Steps>

## Hosted checkout in sandbox

A checkout session started with a sandbox API key opens the hosted page in **test mode**, with helpers built in so you don't have to memorize the test values above:

* A **Test mode** badge in the page header.
* The mobile-money **test numbers** (`0001` fails, `0002` asks for an OTP, `0005` is declined) are listed on the mobile-money screen.
* The **OTP hint** (`123456`) is shown when an OTP is required.
* **Simulate transfer** / **Simulate failed transfer** buttons on the bank-transfer screen finalize the deposit without you needing to leave the page.
* Card fields are pre-filled with a valid **test card** in read-only form.

<Tip>
  Want to try the hosted checkout end to end without touching the API first? Open the [sandbox demo](https://sandbox.pay.afriex.com/demo), pick a currency and amount, and click **Proceed to payment**. All three channels are covered, allocated per currency: **mobile money** (KES, GHS and others), **bank transfer** (NGN), and **card** (USD). Use the same test values documented above to drive different outcomes.
</Tip>

## Funding your sandbox balance

Payouts and swaps need funds in your sandbox wallets. Call [Top Up Sandbox Balance](/api-reference/endpoint/balance/topup) whenever you need more:

```bash theme={null}
curl -X POST \
  -H "x-api-key: YOUR_SANDBOX_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"currency": "USD", "amount": 10000}' \
  https://sandbox.api.afriex.com/api/v1/org/balance/topup
```

The topup endpoint only works in sandbox; production returns `403`.

## Drilling your webhook handler

Verify signatures, retries, and downstream handling with a synthetic event from [Trigger Test Webhook](/api-reference/endpoint/webhooks/trigger). Configure your webhook URL in the [Dashboard](https://business.afriex.com/) first.

## Checklist before going live

Once you're comfortable in sandbox:

<Steps>
  <Step title="Exercise every branch">
    Run a `success`, a `failed`, an OTP flow (`0002`), and a `0003` pending flow. Confirm your webhook handler branches correctly on each.
  </Step>

  <Step title="Verify signatures">
    Trigger a test webhook and confirm you reject a tampered body and accept a valid one.
  </Step>

  <Step title="Reconcile with `merchantReference`">
    Look up transactions by `merchantReference` and confirm your idempotency keys behave as expected on retry.
  </Step>

  <Step title="Switch base URL and keys">
    Swap `sandbox.api.afriex.com` for `api.afriex.com` and your sandbox key for a production key. No other code changes are required.
  </Step>
</Steps>

<Card title="Something behaving differently?" icon="life-ring" href="mailto:support@afriex.com">
  Sandbox and production run the same code paths. If you see divergence, email `support@afriex.com` with the request id and we'll dig in.
</Card>


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.