Virtual Account
Create temporary bank accounts to receive payments via bank transfer. When a customer transfers money to a virtual account, the payment is automatically matched and settled to your account.
Required permission: virtual_account
Create Virtual Account
POST /v1/checkout/virtual-accountAuthentication
Requires a secret key (sk_*).
Request body
| Parameter | Type | Required | Description |
|---|---|---|---|
amount | string | Yes | Expected amount in naira (e.g., "5000.00") |
validity_minutes | integer | No | How long the account stays active. Default: 4320 (72h). Max: 4320 |
amount_validation | string | No | Amount validation rule. See Dynamic PayID amount rules. Default: the bank's own default |
metadata | object | No | Custom key-value data stored with the account |
Settlement
Payments received on this virtual account are settled to your API key's configured settlement ledger. To change where payments are settled, update your API key's settlement configuration in the dashboard.
Example request
curl -X POST https://api.zevpaycheckout.com/v1/checkout/virtual-account \
-H "Content-Type: application/json" \
-H "x-api-key: sk_test_your_secret_key" \
-d '{
"amount": "5000.00",
"validity_minutes": 1440,
"metadata": {
"order_id": "ORD-12345"
}
}'const response = await fetch(
"https://api.zevpaycheckout.com/v1/checkout/virtual-account",
{
method: "POST",
headers: {
"Content-Type": "application/json",
"x-api-key": "sk_test_your_secret_key",
},
body: JSON.stringify({
amount: "5000.00",
validity_minutes: 1440,
metadata: { order_id: "ORD-12345" },
}),
}
);
const data = await response.json();Response
{
"success": true,
"data": {
"public_id": "abc123def456ghi78",
"account_number": "4601234567",
"account_name": "My Business",
"bank_name": "9 Payment Service Bank",
"bank_code": "000036",
"merchant_name": "My Business",
"provider": "psb9",
"reference": "ZVP-VA-1772906868046-d5aa1c",
"amount": "5000.00",
"status": "pending",
"expires_at": "2026-03-09T12:00:00.000Z",
"completed_at": null,
"source": "api",
"created_at": "2026-03-08T12:00:00.000Z"
}
}Show your customer both account_number and bank_name. They must pick that exact bank when sending the transfer, or the money will not reach the account.
Never hardcode the bank
The bank is not fixed. ZevPay mints virtual accounts through partner banks, we add and rotate partners over time, and two accounts created minutes apart can sit at different banks. Always read bank_name (and bank_code for a programmatic bank picker) from the response for that specific account. The provider field is an internal key, kept only for backward compatibility. Do not display it or map it to a bank name yourself.
List Virtual Accounts
GET /v1/checkout/virtual-accountQuery parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
page | integer | 1 | Page number |
page_size | integer | 20 | Items per page (max 100) |
status | string | none | Filter: "pending", "completed", "expired", "cancelled" |
Example request
curl "https://api.zevpaycheckout.com/v1/checkout/virtual-account?status=pending&page_size=10" \
-H "x-api-key: sk_test_your_secret_key"Response
{
"success": true,
"data": {
"items": [
{
"public_id": "abc123def456ghi78",
"account_number": "4601234567",
"account_name": "My Business",
"bank_name": "9 Payment Service Bank",
"bank_code": "000036",
"merchant_name": "My Business",
"provider": "psb9",
"reference": "ZVP-VA-1772906868046-d5aa1c",
"amount": "5000.00",
"status": "pending",
"expires_at": "2026-03-09T12:00:00.000Z",
"completed_at": null,
"source": "api",
"created_at": "2026-03-08T12:00:00.000Z"
}
],
"total": 1,
"page": 1,
"page_size": 10,
"total_pages": 1
}
}Get Virtual Account
GET /v1/checkout/virtual-account/:publicIdPath parameters
| Parameter | Type | Description |
|---|---|---|
publicId | string | Virtual account public ID |
Example request
curl https://api.zevpaycheckout.com/v1/checkout/virtual-account/abc123def456ghi78 \
-H "x-api-key: sk_test_your_secret_key"Status lifecycle
| Status | Description |
|---|---|
pending | Account is active and awaiting payment |
completed | Payment received and settled |
expired | Account expired without receiving payment |
cancelled | Account creation failed at the bank |
Response fields
| Field | Type | Description |
|---|---|---|
public_id | string | Unique public identifier |
account_number | string|null | Bank account number your customer transfers to |
bank_name | string | Bank your customer must select. Varies per account, always read it here |
bank_code | string|null | NIP code for that bank, for a programmatic bank picker |
account_name | string|null | Name shown on the account |
merchant_name | string|null | Same as account_name. Kept for backward compatibility |
provider | string | Internal partner key. Deprecated, never display it |
reference | string | Unique internal reference |
amount | string|null | Expected payment amount (naira) |
status | string | Account status (see lifecycle) |
expires_at | string|null | ISO 8601 expiry timestamp |
completed_at | string|null | ISO 8601 completion timestamp |
source | string | Creation source ("api", "dashboard", etc.) |
created_at | string | ISO 8601 creation timestamp |