Transfer API
Programmatically send money from a wallet to bank accounts or other ZevPay users via PayID.
Security Notice
Transfer endpoints move funds out of your wallet. These endpoints require a secret key and a wallet with programmable debit enabled. Keep your secret keys secure and consider IP whitelisting.
Authentication
All transfer endpoints require:
- A secret key (
sk_live_*orsk_test_*) passed viaAuthorization: Bearer sk_live_... - The API key must have the
transferspermission - A programmable-debit wallet must be linked to the API key
See Authentication for details.
List Banks
Returns all supported banks for external transfers.
GET /v1/checkout/transfer/banksAuthentication
Secret key required. No transfers permission needed.
Example Request
curl https://api.zevpaycheckout.com/v1/checkout/transfer/banks \
-H "Authorization: Bearer sk_live_your_secret_key"const response = await fetch('https://api.zevpaycheckout.com/v1/checkout/transfer/banks', {
headers: {
'Authorization': 'Bearer sk_live_your_secret_key',
},
});
const { data } = await response.json();
console.log(data.banks);import requests
response = requests.get(
'https://api.zevpaycheckout.com/v1/checkout/transfer/banks',
headers={'Authorization': 'Bearer sk_live_your_secret_key'},
)
banks = response.json()['data']['banks']Response
{
"success": true,
"data": {
"banks": [
{
"bankCode": "044",
"bankName": "Access Bank"
},
{
"bankCode": "058",
"bankName": "Guaranty Trust Bank"
}
]
}
}Resolve Bank Account
Verify a bank account number and retrieve the account holder's name (name enquiry).
POST /v1/checkout/transfer/banks/resolveAuthentication
Secret key required. No transfers permission needed.
Request Body
| Parameter | Type | Required | Description |
|---|---|---|---|
account_number | string | Yes | 10-digit bank account number |
bank_code | string | Yes | Bank code from the List Banks endpoint |
Example Request
curl -X POST https://api.zevpaycheckout.com/v1/checkout/transfer/banks/resolve \
-H "Authorization: Bearer sk_live_your_secret_key" \
-H "Content-Type: application/json" \
-d '{
"account_number": "0123456789",
"bank_code": "058"
}'const response = await fetch('https://api.zevpaycheckout.com/v1/checkout/transfer/banks/resolve', {
method: 'POST',
headers: {
'Authorization': 'Bearer sk_live_your_secret_key',
'Content-Type': 'application/json',
},
body: JSON.stringify({
account_number: '0123456789',
bank_code: '058',
}),
});
const { data } = await response.json();
console.log(data.account_name); // "JOHN DOE"import requests
response = requests.post(
'https://api.zevpaycheckout.com/v1/checkout/transfer/banks/resolve',
headers={'Authorization': 'Bearer sk_live_your_secret_key'},
json={
'account_number': '0123456789',
'bank_code': '058',
},
)
data = response.json()['data']
print(data['account_name']) # "JOHN DOE"Response
{
"success": true,
"data": {
"account_name": "JOHN DOE",
"account_number": "0123456789",
"bank_code": "058"
}
}Calculate Charges
Calculate the fees for a bank transfer before initiating it.
POST /v1/checkout/transfer/chargesAuthentication
Secret key required. transfers permission required.
Request Body
| Parameter | Type | Required | Description |
|---|---|---|---|
amount | integer | Yes | Transfer amount in kobo |
bank_code | string | Yes | Destination bank code |
Example Request
curl -X POST https://api.zevpaycheckout.com/v1/checkout/transfer/charges \
-H "Authorization: Bearer sk_live_your_secret_key" \
-H "Content-Type: application/json" \
-d '{
"amount": 500000,
"bank_code": "058"
}'const response = await fetch('https://api.zevpaycheckout.com/v1/checkout/transfer/charges', {
method: 'POST',
headers: {
'Authorization': 'Bearer sk_live_your_secret_key',
'Content-Type': 'application/json',
},
body: JSON.stringify({
amount: 500000,
bank_code: '058',
}),
});
const { data } = await response.json();
console.log(data);Response
{
"success": true,
"data": {
"amount": 500000,
"fee": 2625,
"vat": 197,
"stamp_duty": 0,
"total": 502822,
"currency": "NGN"
}
}Response Fields
| Field | Type | Description |
|---|---|---|
amount | integer | Original transfer amount in kobo |
fee | integer | Transfer fee in kobo |
vat | integer | VAT on the fee in kobo |
stamp_duty | integer | Stamp duty in kobo (applied on amounts ≥ ₦10,000) |
total | integer | Total debit amount (amount + fee + vat + stamp_duty) in kobo |
currency | string | Currency code ("NGN") |
Get Balance
Retrieve the available balance of the linked wallet.
GET /v1/checkout/transfer/balanceAuthentication
Secret key required. transfers permission required.
Example Request
curl https://api.zevpaycheckout.com/v1/checkout/transfer/balance \
-H "Authorization: Bearer sk_live_your_secret_key"const response = await fetch('https://api.zevpaycheckout.com/v1/checkout/transfer/balance', {
headers: {
'Authorization': 'Bearer sk_live_your_secret_key',
},
});
const { data } = await response.json();
console.log(`Balance: ₦${(data.available_balance / 100).toFixed(2)}`);Response
{
"success": true,
"data": {
"available_balance": 15000000,
"currency": "NGN"
}
}| Field | Type | Description |
|---|---|---|
available_balance | integer | Available balance in kobo |
currency | string | Currency code ("NGN") |
Initiate Transfer
Send money to a bank account or ZevPay user. The type field determines the transfer destination.
POST /v1/checkout/transferAuthentication
Secret key required. transfers permission required.
Request Body — Bank Transfer
| Parameter | Type | Required | Description |
|---|---|---|---|
type | string | Yes | Must be "bank_transfer" |
account_number | string | Yes | 10-digit destination account number |
bank_code | string | Yes | Destination bank code |
account_name | string | Yes | Account holder name (from Resolve) |
amount | integer | Yes | Amount in kobo (min: 10,000 = ₦100, max: 500,000,000 = ₦5,000,000) |
narration | string | No | Transfer description (max 100 characters) |
reference | string | No | Your unique reference for idempotency (max 128 characters) |
metadata | object | No | Custom key-value metadata |
Request Body — PayID Transfer
| Parameter | Type | Required | Description |
|---|---|---|---|
type | string | Yes | Must be "payid" |
pay_id | string | Yes | Recipient's ZevPay ID (e.g., "john" or "@john") |
amount | integer | Yes | Amount in kobo (min: 10,000, max: 500,000,000) |
narration | string | No | Transfer description (max 100 characters) |
reference | string | No | Your unique reference for idempotency (max 128 characters) |
metadata | object | No | Custom key-value metadata |
Example Request — Bank Transfer
curl -X POST https://api.zevpaycheckout.com/v1/checkout/transfer \
-H "Authorization: Bearer sk_live_your_secret_key" \
-H "Content-Type: application/json" \
-d '{
"type": "bank_transfer",
"account_number": "0123456789",
"bank_code": "058",
"account_name": "JOHN DOE",
"amount": 500000,
"narration": "Vendor payment",
"reference": "payout_001",
"metadata": {
"invoice_id": "INV-2026-001"
}
}'const response = await fetch('https://api.zevpaycheckout.com/v1/checkout/transfer', {
method: 'POST',
headers: {
'Authorization': 'Bearer sk_live_your_secret_key',
'Content-Type': 'application/json',
},
body: JSON.stringify({
type: 'bank_transfer',
account_number: '0123456789',
bank_code: '058',
account_name: 'JOHN DOE',
amount: 500000,
narration: 'Vendor payment',
reference: 'payout_001',
metadata: { invoice_id: 'INV-2026-001' },
}),
});
const { data } = await response.json();
console.log(data.reference, data.status);import requests
response = requests.post(
'https://api.zevpaycheckout.com/v1/checkout/transfer',
headers={'Authorization': 'Bearer sk_live_your_secret_key'},
json={
'type': 'bank_transfer',
'account_number': '0123456789',
'bank_code': '058',
'account_name': 'JOHN DOE',
'amount': 500000,
'narration': 'Vendor payment',
'reference': 'payout_001',
'metadata': {'invoice_id': 'INV-2026-001'},
},
)
data = response.json()['data']
print(data['reference'], data['status'])Example Request — PayID Transfer
curl -X POST https://api.zevpaycheckout.com/v1/checkout/transfer \
-H "Authorization: Bearer sk_live_your_secret_key" \
-H "Content-Type: application/json" \
-d '{
"type": "payid",
"pay_id": "john",
"amount": 250000,
"narration": "Freelance payment"
}'const response = await fetch('https://api.zevpaycheckout.com/v1/checkout/transfer', {
method: 'POST',
headers: {
'Authorization': 'Bearer sk_live_your_secret_key',
'Content-Type': 'application/json',
},
body: JSON.stringify({
type: 'payid',
pay_id: 'john',
amount: 250000,
narration: 'Freelance payment',
}),
});
const { data } = await response.json();
console.log(data.reference, data.status);Response
{
"success": true,
"data": {
"reference": "ZVP-TRF-abc123def456",
"public_id": "xK9mQ2pL4vR7nW3jY",
"type": "bank_transfer",
"status": "completed",
"amount": 500000,
"fees": 2625,
"vat": 197,
"stamp_duty": 0,
"currency": "NGN",
"narration": "Vendor payment",
"recipient": {
"name": "JOHN DOE",
"bank": "Guaranty Trust Bank",
"account_number": "0123456789"
},
"merchant_reference": "payout_001",
"metadata": {
"invoice_id": "INV-2026-001"
},
"created_at": "2026-03-08T14:30:00.000Z",
"completed_at": "2026-03-08T14:30:02.000Z"
}
}Response Fields
| Field | Type | Description |
|---|---|---|
reference | string | ZevPay transaction reference |
public_id | string | Public transaction identifier |
type | string | "bank_transfer" or "payid" |
status | string | "pending", "completed", or "failed" |
amount | integer | Transfer amount in kobo |
fees | integer | Transfer fees in kobo (0 for PayID transfers) |
vat | integer | VAT in kobo |
stamp_duty | integer | Stamp duty in kobo |
currency | string | Currency code ("NGN") |
narration | string | null | Transfer description |
recipient.name | string | Recipient name |
recipient.bank | string | null | Recipient bank (bank transfers only) |
recipient.account_number | string | null | Recipient account number (bank transfers only) |
merchant_reference | string | null | Your idempotency reference |
metadata | object | null | Your custom metadata |
created_at | string | ISO 8601 timestamp |
completed_at | string | null | ISO 8601 timestamp (when completed) |
Transfer Status Lifecycle
| Status | Description |
|---|---|
pending | Transfer is being processed (bank transfers only) |
completed | Transfer delivered successfully |
failed | Transfer was rejected by the bank |
INFO
PayID transfers complete instantly — the status will always be "completed" on success.
Bank transfers may briefly show "pending" before resolving to "completed" or "failed".
Idempotency
If you provide a reference and a transfer with that reference already exists, the API returns the existing transfer instead of creating a duplicate. The response will be identical to the original.
List Transfers
Retrieve a paginated list of transfers from the linked wallet.
GET /v1/checkout/transferAuthentication
Secret key required. transfers permission required.
Query Parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
page | integer | 1 | Page number |
page_size | integer | 20 | Items per page (max 100) |
status | string | — | Filter by status: pending, completed, failed |
type | string | — | Filter by type: bank_transfer, payid |
search | string | — | Search by reference, recipient name, or account number |
Example Request
curl "https://api.zevpaycheckout.com/v1/checkout/transfer?page=1&page_size=10&status=completed" \
-H "Authorization: Bearer sk_live_your_secret_key"const params = new URLSearchParams({
page: '1',
page_size: '10',
status: 'completed',
});
const response = await fetch(
`https://api.zevpaycheckout.com/v1/checkout/transfer?${params}`,
{
headers: {
'Authorization': 'Bearer sk_live_your_secret_key',
},
},
);
const { data } = await response.json();
console.log(`${data.total} transfers, page ${data.page}/${data.total_pages}`);Response
{
"success": true,
"data": {
"items": [
{
"reference": "ZVP-TRF-abc123def456",
"public_id": "xK9mQ2pL4vR7nW3jY",
"type": "bank_transfer",
"status": "completed",
"amount": 500000,
"fees": 2625,
"vat": 197,
"stamp_duty": 0,
"currency": "NGN",
"narration": "Vendor payment",
"recipient": {
"name": "JOHN DOE",
"bank": "Guaranty Trust Bank",
"account_number": "0123456789"
},
"merchant_reference": "payout_001",
"metadata": null,
"created_at": "2026-03-08T14:30:00.000Z",
"completed_at": "2026-03-08T14:30:02.000Z"
}
],
"total": 42,
"page": 1,
"page_size": 10,
"total_pages": 5
}
}Get Transfer
Retrieve a single transfer by its reference.
GET /v1/checkout/transfer/:referenceAuthentication
Secret key required. transfers permission required.
Path Parameters
| Parameter | Type | Description |
|---|---|---|
reference | string | The transfer reference |
Example Request
curl https://api.zevpaycheckout.com/v1/checkout/transfer/ZVP-TRF-abc123def456 \
-H "Authorization: Bearer sk_live_your_secret_key"const reference = 'ZVP-TRF-abc123def456';
const response = await fetch(
`https://api.zevpaycheckout.com/v1/checkout/transfer/${reference}`,
{
headers: {
'Authorization': 'Bearer sk_live_your_secret_key',
},
},
);
const { data } = await response.json();
console.log(data.status);Response
Returns the same transfer object as the initiate endpoint.
Verify Transfer
Check the current status of a transfer. For bank transfers, this queries the banking provider for the latest status.
GET /v1/checkout/transfer/:reference/verifyAuthentication
Secret key required. transfers permission required.
Path Parameters
| Parameter | Type | Description |
|---|---|---|
reference | string | The transfer reference |
Example Request
curl https://api.zevpaycheckout.com/v1/checkout/transfer/ZVP-TRF-abc123def456/verify \
-H "Authorization: Bearer sk_live_your_secret_key"const reference = 'ZVP-TRF-abc123def456';
const response = await fetch(
`https://api.zevpaycheckout.com/v1/checkout/transfer/${reference}/verify`,
{
headers: {
'Authorization': 'Bearer sk_live_your_secret_key',
},
},
);
const { data } = await response.json();
console.log(data.status); // "completed" | "pending" | "failed"Response
Returns the same transfer object as the initiate endpoint, with the most up-to-date status.
TIP
Use the verify endpoint after receiving a transfer.failed webhook to confirm the final status, or to poll pending transfers that haven't received a webhook yet.
Errors
| Status | Code | Description |
|---|---|---|
| 400 | Bad Request | Invalid parameters (missing fields, invalid amount range) |
| 400 | Bad Request | Transfer not found (wrong reference) |
| 400 | Bad Request | Insufficient balance |
| 400 | Bad Request | Self-transfer not allowed (PayID transfers) |
| 403 | Forbidden | Secret key required |
| 403 | Forbidden | API key missing transfers permission |
| 403 | Forbidden | No transfer wallet linked to API key |
| 403 | Forbidden | Programmable debit disabled on wallet |
| 403 | Forbidden | Wallet does not belong to this business |