---
title: "Simulate a sandbox transaction outcome"
method: POST
path: "/api/v1/transaction/{transactionId}/simulate"
tags: ["Sandbox helpers"]
---

# Simulate a sandbox transaction outcome

`POST /api/v1/transaction/{transactionId}/simulate`

## Request

- Base URL: `https://sandbox.api.afriex.com`
- URL: `https://sandbox.api.afriex.com/api/v1/transaction/{transactionId}/simulate`
- Auth: API key in header `x-api-key`
- TypeScript: <https://skmtc.dev/afriex/apis/afriex-business-api/docs/api/v1/transaction/:transactionId/simulate/post.ts> (a function with typed inputs; its response is checked against the schema)

Sandbox only; returns 403 in production. Completes a pending sandbox deposit or payout with the outcome you choose, and your balance and transaction webhook behave as they would in production. The transaction must still be `PENDING`, `PROCESSING` or `UNKNOWN`: a deposit waiting for an OTP must be authorized first. Use it for a payment method whose account number, phone number or card ends in `0003`, which stays pending until simulated, or to complete any other pending sandbox transaction early. Only sandbox transactions that are still pending can be simulated. The result arrives by webhook shortly after this call returns. Your business must have a webhook URL set, otherwise the request is rejected with a 400.

## Path parameters

- `transactionId` string, required

## Request body

- object
  - `outcome` 'success' | 'failed', required: The status to finalize the transaction to.

## Response `202`

Accepted. The transaction as it stands before it is finalized.

- object
  - `data` Transaction
    - `transactionId` string: The unique identifier for the transaction.
    - `customerId` string: The unique identifier of the customer.
    - `sourceId` string: The unique identifier of the source payment method.
    - `destinationId` string: The unique identifier of the destination payment method.
    - `sourceAmount` string: The souce transaction amount value
    - `sourceCurrency` string: The currency code of the wallet charged.
    - `destinationAmount` string: The destination transaction amount value
    - `destinationCurrency` string: The description currency code.
    - `type` 'DEPOSIT' | 'WITHDRAW' | 'SWAP' | 'REVERSAL' | 'SCHEDULED' | 'SEND': The type of transaction. You can create `DEPOSIT`, `WITHDRAW` and `SWAP`; the other values can appear on transactions listed or fetched for your business.
    - `channel` 'BANK_ACCOUNT' | 'SWIFT' | 'MOBILE_MONEY' | 'UPI' | 'INTERAC' | 'WE_CHAT' | 'CARD' | 'CRYPTO' | 'VIRTUAL_BANK_ACCOUNT' | 'POOL_ACCOUNT' | 'ACH_BANK_ACCOUNT' | 'PAYBILL_TILL' | 'RFP' | 'VIRTUAL_CARD' | 'ALIPAY' | 'WALLET' | 'PAYMENT_LINQ' | 'ADMIN': The payment channel of the transaction. `ADMIN` marks an adjustment Afriex made to your wallet; `PAYMENT_LINQ` a payment made through a payment link.
    - `status` 'PENDING' | 'PROCESSING' | 'SUCCESS' | 'FAILED' | 'CANCELLED' | 'REFUNDED' | 'RETRY' | 'UNKNOWN' | 'SCHEDULED' | 'CUSTOMER_ACTION_REQUIRED' | 'REJECTED' | 'IN_REVIEW' | 'RFI_REQUESTED' | 'DISPUTED' | 'DISPUTE_RESOLVED' | 'DISPUTE_WON' | 'DISPUTE_LOST' | 'DISPUTE_EVIDENCE_SUBMITTED': The current status of the transaction. `IN_REVIEW` and `RFI_REQUESTED` are review states: the transaction is still in flight and is waiting on a review, not on you. Treat them as non-terminal and keep polling or listening for `TRANSACTION.UPDATED`. `RFI_REQUESTED` may result in someone contacting you for more information about the transfer.
    - `merchantReference` string: The merchant-supplied reference for the transaction (mirrors meta.reference from the create request).
    - `rate` string: The realized source-to-destination exchange rate for the transaction, expressed as `1 sourceCurrency = rate destinationCurrency` (equal to destinationAmount / sourceAmount).
    - `fee` string: The fee charged for this transaction, denominated in `sourceCurrency`. It is reported separately from `sourceAmount`. Omitted when no fee applied to the transaction.
    - `meta` object: Transaction metadata. Echoes the metadata you attached on create and may include server-set state flags such as `otpRequired` and `failureReason`.
      - `narration` string: The narration you attached when creating the transaction, echoed back. An empty string when none was provided.
      - `otpRequired` boolean: Returned on deposits that may need an extra authorization step. When `true`, the deposit is waiting for the customer to submit a one-time password; call `POST /transaction/{transactionId}/authorize` to complete it.
      - `failureReason` object: Present only when `status` is `FAILED` or `REJECTED`. Carries a stable `AFX_*` code and a customer-safe message. Branch on `code` rather than the underlying rail so your integration stays stable across routing changes.
        - `code` 'AFX_REQUEST_FAILED' | 'AFX_SYSTEM_ERROR' | 'AFX_SERVICE_UNAVAILABLE' | 'AFX_INVALID_CURRENCY' | 'AFX_INVALID_AMOUNT' | 'AFX_INVALID_RECIPIENT' | 'AFX_RECIPIENT_NOT_FOUND' | 'AFX_ACCOUNT_CLOSED' | 'AFX_NAME_MISMATCH' | 'AFX_BENEFICIARY_RESTRICTED' | 'AFX_INVALID_SENDER' | 'AFX_INVALID_REQUEST' | 'AFX_VELOCITY_LIMIT_EXCEEDED' | 'AFX_AMOUNT_LIMIT_EXCEEDED' | 'AFX_RECIPIENT_WALLET_LIMIT_REACHED' | 'AFX_PAYMENT_FAILED' | 'AFX_COMPLIANCE_REJECTED' | 'AFX_PROPOSAL_EXPIRED', required: Stable `AFX_*` failure code. Safe to switch on; the set grows over time but existing values do not change meaning.
        - `message` string, required: Customer-safe short description of the failure. Suitable for display; do not parse it. Branch on `code` instead.
        - `retryable` boolean, required: `true` when re-submitting the same request (or, for `AFX_PROPOSAL_EXPIRED`, starting a fresh proposal) may succeed. `false` when the caller must change the request before retrying.
    - `createdAt` string: The date and time the transaction was created.
    - `updatedAt` string: The date and time the transaction was last updated.

## Other responses

- `400`: The transaction cannot be simulated: it is not a sandbox transaction that can be simulated, is waiting for authorization, or is already completed. Also returned when your business is not yet allowed to use the API (no webhook URL set, request IP not on your whitelist, business not approved or deactivated, or verification required).
- `401`: Unauthorized.
- `403`: Not available in production. The body is `{"message": "Not allowed"}`, not the usual error shape.
- `404`: Transaction not found.
- `500`: Server error.
- `503`: Temporarily unavailable: an upstream service failed or timed out, or the API is momentarily offline. Retry with backoff and honour the Retry-After header when present.

---

[API](https://skmtc.dev/afriex/apis/afriex-business-api.md) - [All operations](https://skmtc.dev/afriex/apis/afriex-business-api/llms.txt) - [OpenAPI document](https://skmtc.dev/afriex/apis/afriex-business-api/revisions/d94198b6c256?raw)
