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

### Authentication

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](/api/dynamic-payid#amount-validation-rules). Default: the bank's own default |
| `metadata` | object | No | Custom key-value data stored with the account |

::: tip 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](https://dashboard.zevpaycheckout.com).
:::

### Example request

::: code-group

```bash [cURL]
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"
    }
  }'
```

```javascript [Node.js]
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

```json
{
  "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.

::: warning 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-account
```

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

```bash
curl "https://api.zevpaycheckout.com/v1/checkout/virtual-account?status=pending&page_size=10" \
  -H "x-api-key: sk_test_your_secret_key"
```

### Response

```json
{
  "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/:publicId
```

### Path parameters

| Parameter | Type | Description |
|-----------|------|-------------|
| `publicId` | string | Virtual account public ID |

### Example request

```bash
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](#status-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 |

## Try it: Create Virtual Account

<ApiPlayground
  method="POST"
  endpoint="/v1/checkout/virtual-account"
  :bodyFields="[
    { name: 'amount', type: 'string', required: true, placeholder: '5000.00', description: 'Amount in naira' },
    { name: 'validity_minutes', type: 'integer', placeholder: '4320', description: 'Default: 4320 (72h)' },
    { name: 'metadata', type: 'object', placeholder: '{ &quot;order_id&quot;: &quot;ORD-12345&quot; }' },
  ]"
/>

## Try it: List Virtual Accounts

<ApiPlayground
  method="GET"
  endpoint="/v1/checkout/virtual-account"
  :queryParams="[
    { name: 'page', type: 'integer', placeholder: '1', default: '1' },
    { name: 'page_size', type: 'integer', placeholder: '20' },
    { name: 'status', type: 'select', options: [{ label: 'Pending', value: 'pending' }, { label: 'Completed', value: 'completed' }, { label: 'Expired', value: 'expired' }, { label: 'Cancelled', value: 'cancelled' }] },
  ]"
/>
