curl --request POST \
--url https://sandbox.api.afriex.com/api/v1/payment-method/virtual-account/simulate-transfer \
--header 'Content-Type: application/json' \
--header 'x-api-key: <api-key>' \
--data '
{
"accountNumber": "4821930275",
"amount": 5000,
"currency": "NGN"
}
'import requests
url = "https://sandbox.api.afriex.com/api/v1/payment-method/virtual-account/simulate-transfer"
payload = {
"accountNumber": "4821930275",
"amount": 5000,
"currency": "NGN"
}
headers = {
"x-api-key": "<api-key>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {'x-api-key': '<api-key>', 'Content-Type': 'application/json'},
body: JSON.stringify({accountNumber: '4821930275', amount: 5000, currency: 'NGN'})
};
fetch('https://sandbox.api.afriex.com/api/v1/payment-method/virtual-account/simulate-transfer', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://sandbox.api.afriex.com/api/v1/payment-method/virtual-account/simulate-transfer",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_POSTFIELDS => json_encode([
'accountNumber' => '4821930275',
'amount' => 5000,
'currency' => 'NGN'
]),
CURLOPT_HTTPHEADER => [
"Content-Type: application/json",
"x-api-key: <api-key>"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"strings"
"net/http"
"io"
)
func main() {
url := "https://sandbox.api.afriex.com/api/v1/payment-method/virtual-account/simulate-transfer"
payload := strings.NewReader("{\n \"accountNumber\": \"4821930275\",\n \"amount\": 5000,\n \"currency\": \"NGN\"\n}")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("x-api-key", "<api-key>")
req.Header.Add("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.post("https://sandbox.api.afriex.com/api/v1/payment-method/virtual-account/simulate-transfer")
.header("x-api-key", "<api-key>")
.header("Content-Type", "application/json")
.body("{\n \"accountNumber\": \"4821930275\",\n \"amount\": 5000,\n \"currency\": \"NGN\"\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://sandbox.api.afriex.com/api/v1/payment-method/virtual-account/simulate-transfer")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["x-api-key"] = '<api-key>'
request["Content-Type"] = 'application/json'
request.body = "{\n \"accountNumber\": \"4821930275\",\n \"amount\": 5000,\n \"currency\": \"NGN\"\n}"
response = http.request(request)
puts response.read_body{
"data": {
"reference": "<string>"
}
}{
"code": "<string>",
"error": "<string>",
"details": {
"errorMessage": "<string>",
"friendlyMessage": "<string>",
"data": {
"customerId": "<string>"
}
}
}{
"code": "<string>",
"error": "<string>",
"details": {
"errorMessage": "<string>",
"friendlyMessage": "<string>",
"data": {
"customerId": "<string>"
}
}
}Simulate Virtual Account Transfer
Sandbox only; returns 403 in production. Credits a bank transfer into one of your sandbox virtual accounts, the way the account provider’s deposit notification would, so balances and your transaction webhook behave as in production. Pass the account number; for an account created with an amount (a one-time account), also pass the reference it was issued with. The deposit is reported by the transaction webhook.
curl --request POST \
--url https://sandbox.api.afriex.com/api/v1/payment-method/virtual-account/simulate-transfer \
--header 'Content-Type: application/json' \
--header 'x-api-key: <api-key>' \
--data '
{
"accountNumber": "4821930275",
"amount": 5000,
"currency": "NGN"
}
'import requests
url = "https://sandbox.api.afriex.com/api/v1/payment-method/virtual-account/simulate-transfer"
payload = {
"accountNumber": "4821930275",
"amount": 5000,
"currency": "NGN"
}
headers = {
"x-api-key": "<api-key>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {'x-api-key': '<api-key>', 'Content-Type': 'application/json'},
body: JSON.stringify({accountNumber: '4821930275', amount: 5000, currency: 'NGN'})
};
fetch('https://sandbox.api.afriex.com/api/v1/payment-method/virtual-account/simulate-transfer', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://sandbox.api.afriex.com/api/v1/payment-method/virtual-account/simulate-transfer",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_POSTFIELDS => json_encode([
'accountNumber' => '4821930275',
'amount' => 5000,
'currency' => 'NGN'
]),
CURLOPT_HTTPHEADER => [
"Content-Type: application/json",
"x-api-key: <api-key>"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"strings"
"net/http"
"io"
)
func main() {
url := "https://sandbox.api.afriex.com/api/v1/payment-method/virtual-account/simulate-transfer"
payload := strings.NewReader("{\n \"accountNumber\": \"4821930275\",\n \"amount\": 5000,\n \"currency\": \"NGN\"\n}")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("x-api-key", "<api-key>")
req.Header.Add("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.post("https://sandbox.api.afriex.com/api/v1/payment-method/virtual-account/simulate-transfer")
.header("x-api-key", "<api-key>")
.header("Content-Type", "application/json")
.body("{\n \"accountNumber\": \"4821930275\",\n \"amount\": 5000,\n \"currency\": \"NGN\"\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://sandbox.api.afriex.com/api/v1/payment-method/virtual-account/simulate-transfer")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["x-api-key"] = '<api-key>'
request["Content-Type"] = 'application/json'
request.body = "{\n \"accountNumber\": \"4821930275\",\n \"amount\": 5000,\n \"currency\": \"NGN\"\n}"
response = http.request(request)
puts response.read_body{
"data": {
"reference": "<string>"
}
}{
"code": "<string>",
"error": "<string>",
"details": {
"errorMessage": "<string>",
"friendlyMessage": "<string>",
"data": {
"customerId": "<string>"
}
}
}{
"code": "<string>",
"error": "<string>",
"details": {
"errorMessage": "<string>",
"friendlyMessage": "<string>",
"data": {
"customerId": "<string>"
}
}
}403 in production.TRANSACTION.UPDATED webhook behave the same.
Sandbox virtual accounts receive no money on their own, so use this endpoint whenever you want to test what happens after a customer funds one.
Choosing the account
- Reusable virtual account (created without
amount): pass theaccountNumberandamountto credit. - One-time virtual account (created with
amount): pass the samereferencethe account was issued with, in addition toaccountNumberandamount.
Controlling the outcome
outcome defaults to success. Pass "outcome": "failed" to simulate a failed inflow.
The deposit lands as a transaction and is reported by webhook. See the Sandbox testing guide for how virtual accounts fit into the broader sandbox model.Authorizations
Static business API key issued from the dashboard. A business can
provision multiple API keys, each scoped to a configurable set of
permissions (e.g. read transactions, create deposits, etc). Permissions are chosen per key at creation time in the
dashboard and may be revoked by deleting the key. Requests made with a
key that does not include the permission required by the target
endpoint is rejected with a 401 Unauthorized response, the same
response an unrecognised, malformed, revoked or disabled key returns. The API
does not distinguish the two cases on the wire.
Manage your keys and their permissions under Developer → API keys
in the dashboard.
The permission each endpoint requires is below. Where several are listed, any one of them is enough. Keys created before permissions existed carry none and keep access to every endpoint.
| Endpoint | Permission |
|---|---|
GET /customer, GET /customer/{customerId} | CUSTOMER.READ |
POST /customer | CUSTOMER.CREATE |
PATCH /customer/{customerId}, PATCH /customer/{customerId}/kyc, POST /customer/{customerId}/verify | CUSTOMER.UPDATE |
DELETE /customer/{customerId} | CUSTOMER.DELETE |
GET /payment-method, GET /payment-method/{paymentMethodId}, GET /payment-method/institution, GET /payment-method/institution/codes, GET /payment-method/resolve, GET /payment-method/virtual-account, GET /payment-method/pool-account | PAYMENT_METHOD.READ |
POST /payment-method, DELETE /payment-method/{paymentMethodId}, POST /payment-method/virtual-account, GET /payment-method/crypto-wallet, POST /payment-method/virtual-account/simulate-transfer (sandbox only) | PAYMENT_METHOD.CREATE |
POST /transaction | TRANSACTION.DEPOSIT.CREATE, TRANSACTION.WITHDRAW.CREATE or TRANSACTION.SWAP.CREATE |
POST /transaction/{transactionId}/authorize, POST /transaction/pool-account | TRANSACTION.DEPOSIT.CREATE |
POST /transaction/{transactionId}/simulate (sandbox only) | TRANSACTION.DEPOSIT.CREATE or TRANSACTION.WITHDRAW.CREATE |
GET /transaction, GET /transaction/{transactionId}, GET /transaction/{transactionId}/advice | TRANSACTION.HISTORY.READ |
GET /org/balance, GET /org/rates | WALLET.BALANCES.READ |
POST /media/url | PAYMENT_METHOD.CREATE |
POST /sme-registration | COMPLIANCE.KYB.SUBMIT |
GET /sme-registration/status | COMPLIANCE.KYB.READ |
POST /checkout-session | CHECKOUT_LINK.CREATE |
GET /payment-batch, GET /payment-batch/{batchId}, GET /payment-batch/{batchId}/recipients | PAYMENT_METHOD.READ |
POST /payment-batch, PATCH /payment-batch/{batchId}, DELETE /payment-batch/{batchId}, POST /payment-batch/{batchId}/recipients, POST /payment-batch/{batchId}/recipients/bulk, PATCH /payment-batch/{batchId}/recipients/{recipientId}, DELETE /payment-batch/{batchId}/recipients/{recipientId} | PAYMENT_METHOD.CREATE |
POST /payment-batch/{batchId}/withdraw | TRANSACTION.WITHDRAW.CREATE |
GET /payment-batch/{batchId}/sessions | TRANSACTION.HISTORY.READ |
POST /org/balance/topup, POST /webhooks/trigger | None; any valid key |
Headers
API version in ISO 8601 format. The only supported version is 2026-05-18, which is also the default when the header is omitted. Any other value is rejected with a 400 Bad Request.
Body
The virtual account number to pay into.
The amount transferred.
The account's currency.
The reference a one-time (amount-bound) account was issued with. Not needed for a reusable account.
Defaults to success.
success, failed Response
Accepted. The reference of the new deposit.
Show child attributes
Show child attributes
