> ## 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.
  Payouts, mobile-money deposits, card deposits, and virtual accounts can all be tested in every currency your business supports.
</Info>

## 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 **bank account number**, the **mobile money 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.

| 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).
  </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>

## 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>
