const account = await afriex.paymentMethods.createVirtualAccount({
currency: "USD",
label: "SALES",
customerId: "optional-customer-id",
});
curl --request POST \
--url https://sandbox.api.afriex.com/api/v1/payment-method/virtual-account \
--header 'Content-Type: application/json' \
--header 'x-api-key: <api-key>' \
--data '
{
"currency": "NGN",
"customerId": "68e6717848e1f632e9686460",
"label": "SALES"
}
'import requests
url = "https://sandbox.api.afriex.com/api/v1/payment-method/virtual-account"
payload = {
"currency": "NGN",
"customerId": "68e6717848e1f632e9686460",
"label": "SALES"
}
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({currency: 'NGN', customerId: '68e6717848e1f632e9686460', label: 'SALES'})
};
fetch('https://sandbox.api.afriex.com/api/v1/payment-method/virtual-account', 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",
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([
'currency' => 'NGN',
'customerId' => '68e6717848e1f632e9686460',
'label' => 'SALES'
]),
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"
payload := strings.NewReader("{\n \"currency\": \"NGN\",\n \"customerId\": \"68e6717848e1f632e9686460\",\n \"label\": \"SALES\"\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")
.header("x-api-key", "<api-key>")
.header("Content-Type", "application/json")
.body("{\n \"currency\": \"NGN\",\n \"customerId\": \"68e6717848e1f632e9686460\",\n \"label\": \"SALES\"\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://sandbox.api.afriex.com/api/v1/payment-method/virtual-account")
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 \"currency\": \"NGN\",\n \"customerId\": \"68e6717848e1f632e9686460\",\n \"label\": \"SALES\"\n}"
response = http.request(request)
puts response.read_body{
"data": {}
}Create Virtual Account
Creates a new virtual account for the resolved customer-or-owner and
currency. Pass customerId to mint the VA for a specific end-user;
omit it to mint one for the business owner.
Supported currencies. A virtual account for one of your customers
(customerId supplied) can be created in NGN or GHS. Any other
currency returns 422 UNSUPPORTED_VIRTUAL_ACCOUNT_CURRENCY; create it for
the business itself by omitting customerId, which is how KES and
USD accounts are created.
Subject to a per-(business, customer, currency) limit. Requests that
would exceed the limit return 403 VIRTUAL_ACCOUNT_LIMIT_REACHED.
NGN and BVN. A permanent NGN account (no amount) is opened
against the account holder’s BVN. If the holder has none, the request
returns VIRTUAL_ACCOUNT_BVN_REQUIRED; pass amount to get a one-time
account instead, which does not need a BVN.
Compliance review: every currency requires a compliance-approved
application before issuance. The first request for a not-yet-approved
business/currency starts that review and returns
VIRTUAL_ACCOUNT_PENDING_COMPLIANCE_REVIEW; retry once approved.
Accounts opened asynchronously: for some currencies the issuing
bank opens the account after the request returns. The response is
then 201 with an empty data object, and the account is delivered
through the PAYMENT_METHOD.CREATED webhook once it is open.
Label vs amount: label groups static virtual accounts by
purpose (e.g. SALES, OPERATIONS). It is mutually exclusive
with amount, which routes to a dynamic (ephemeral) virtual
account. Supplying both is rejected at validation.
const account = await afriex.paymentMethods.createVirtualAccount({
currency: "USD",
label: "SALES",
customerId: "optional-customer-id",
});
curl --request POST \
--url https://sandbox.api.afriex.com/api/v1/payment-method/virtual-account \
--header 'Content-Type: application/json' \
--header 'x-api-key: <api-key>' \
--data '
{
"currency": "NGN",
"customerId": "68e6717848e1f632e9686460",
"label": "SALES"
}
'import requests
url = "https://sandbox.api.afriex.com/api/v1/payment-method/virtual-account"
payload = {
"currency": "NGN",
"customerId": "68e6717848e1f632e9686460",
"label": "SALES"
}
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({currency: 'NGN', customerId: '68e6717848e1f632e9686460', label: 'SALES'})
};
fetch('https://sandbox.api.afriex.com/api/v1/payment-method/virtual-account', 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",
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([
'currency' => 'NGN',
'customerId' => '68e6717848e1f632e9686460',
'label' => 'SALES'
]),
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"
payload := strings.NewReader("{\n \"currency\": \"NGN\",\n \"customerId\": \"68e6717848e1f632e9686460\",\n \"label\": \"SALES\"\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")
.header("x-api-key", "<api-key>")
.header("Content-Type", "application/json")
.body("{\n \"currency\": \"NGN\",\n \"customerId\": \"68e6717848e1f632e9686460\",\n \"label\": \"SALES\"\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://sandbox.api.afriex.com/api/v1/payment-method/virtual-account")
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 \"currency\": \"NGN\",\n \"customerId\": \"68e6717848e1f632e9686460\",\n \"label\": \"SALES\"\n}"
response = http.request(request)
puts response.read_body{
"data": {}
}Supported currencies
You can create virtual accounts inNGN, GHS, KES, and USD. The currency decides who can hold the account:
| Currency | Who can hold it | Notes |
|---|---|---|
NGN (Nigeria) | Your customers or your business | Static accounts for a customer need a BVN. See BVN requirement. |
GHS (Ghana) | Your customers or your business | |
KES (Kenya) | Your business only (omit customerId) | To have KES accounts issued under your own legal entity, complete SME registration. |
USD | Your business only (omit customerId) |
NGN and GHS accounts. Requesting any other currency with a customerId returns UNSUPPORTED_VIRTUAL_ACCOUNT_CURRENCY with 422. To create a KES, USD, or other supported currency account, omit customerId so the account is minted for the business itself.label) or one-time (amount). Requesting a type that is not offered for the currency returns UNSUPPORTED_VIRTUAL_ACCOUNT_TYPE with 422.
Static vs dynamic virtual accounts
You choose between two flavours of virtual account by what you pass alongsidecurrency and customerId.
Static (label)
Pass alabel to group static virtual accounts by purpose. Static accounts are permanently assigned to the customer and never expire. The same account number remains valid for repeated funding.
Allowed labels: SALES, OPERATIONS, PAYROLL, COLLECTIONS, VENDOR_PAYMENTS, TAX, REFUNDS, MARKETING, TREASURY, GENERAL.
Use static accounts when you want stable, long-lived account numbers a customer can top up over time, organised by purpose.
Dynamic (amount)
Pass anamount to mint a dynamic (ephemeral) virtual account tied to that specific amount. Dynamic accounts expire after a short window. The amount must be greater than 0 and at most 5,000,000.
Use dynamic accounts when you need to collect a specific, known amount within a defined window, like a one-time payment for a particular order.
label and amount are mutually exclusive. Supplying both is rejected at validation.BVN requirement for NGN static virtual accounts
For NGN static virtual accounts (label-based, Nigeria), the customer must have a Bank Verification Number (BVN) on file. This is a regulatory requirement for permanently assigned accounts in Nigeria. If the BVN is missing, the API returns400 with VIRTUAL_ACCOUNT_BVN_REQUIRED.
You can supply a BVN when creating a new customer or add it to an existing customer at any time using the Update Customer KYC endpoint.
Limits
Creation is subject to a per-(business, customer, currency) cap. Requests that would exceed the cap returnVIRTUAL_ACCOUNT_LIMIT_REACHED with 403. Call List Virtual Accounts first to check what already exists for the customer.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. Manage your keys and their permissions under Developer → API keys in the dashboard.
A request whose key does not carry the permission the target
endpoint requires is rejected with 401 Unauthorized, the same
response an unrecognised, malformed, revoked or disabled key
returns. The API does not distinguish the two cases on the wire.
See the Permissions reference for the permission each endpoint requires.
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.
Signature of the request payload, used for request authentication where payload signing is enabled for your business.
Body
The 3-letter ISO 4217 currency code for the virtual account. The listed values are the supported currencies; other valid currency codes pass validation and fail later in the request.
NGN, GHS, KES, USD Optional customer ID. When supplied, the VA is created for that customer. When omitted, it is created for the business owner.
Optional amount for a dynamic (ephemeral) virtual
account. Mutually exclusive with label.
x <= 5000000Optional label that groups static virtual accounts by
purpose. Mutually exclusive with amount.
SALES, OPERATIONS, PAYROLL, COLLECTIONS, VENDOR_PAYMENTS, TAX, REFUNDS, MARKETING, TREASURY, GENERAL Response
Virtual account created successfully, or (empty data) requested
and pending with the issuing bank.
A payment method. Empty fields are omitted. Account-shaped channels (BANK_ACCOUNT, MOBILE_MONEY, SWIFT, etc.) populate accountName, accountNumber, and institution; the CARD channel instead populates last4, brand, expiration, and cardName.
- Option 1
- Option 2
Show child attributes
Show child attributes
