Static NGN Account
Issue permanent bank account numbers to your customers and receive every transfer as a booked payment with an instant webhook. See the concept guide for how the product behaves.
All endpoints require a secret key (sk_*). Static NGN Accounts are enabled for selected merchants on request; contact our business support team for access. Every endpoint except GET /status returns 403 with error.code = "STATIC_ACCOUNT_NOT_ENABLED" when the product is not enabled for your account; GET /status always answers so you can check before you build.
Base path: /v1/checkout/static-account
Check availability
GET /v1/checkout/static-account/status{
"success": true,
"data": {
"enabled": true,
"currency": "NGN",
"bank_names": ["PalmPay"],
"identity_types": ["bvn", "nin", "rc"]
}
}bank_names lists the bank(s) new accounts will be issued at, so you can show the customer where to transfer.
Create an account
POST /v1/checkout/static-accountRequest body
| Parameter | Type | Required | Description |
|---|---|---|---|
customer_name | string | Yes | Full legal name of the account holder, as on the identity record (2 to 200 characters) |
identity_type | string | Yes | "bvn", "nin" or "rc" |
identity_number | string | Yes | 11-digit BVN or NIN, or the CAC number prefixed RC / BN (e.g. "RC1234567") |
customer_email | string | No | Account holder's email |
customer_phone | string | No | Account holder's phone |
account_name | string | No | Name printed on the account. Defaults to customer_name. The bank may append your business suffix |
reference | string | Yes | Your unique reference for this account, typically your customer id (max 120 chars). Idempotent: sending the same reference again returns the existing account instead of creating another, so a customer never ends up with several numbers |
metadata | object | No | Custom key-value data stored with the account and echoed on every charge.success |
The name is checked against the identity record before anything is created. If it does not match, the request fails with IDENTITY_MISMATCH.
Example request
curl -X POST https://api.zevpaycheckout.com/v1/checkout/static-account \
-H "Content-Type: application/json" \
-H "x-api-key: sk_live_your_secret_key" \
-d '{
"customer_name": "Adaeze Okafor",
"identity_type": "bvn",
"identity_number": "22345678901",
"customer_email": "adaeze@example.com",
"reference": "customer-1042",
"metadata": { "plan": "pro" }
}'const response = await fetch(
"https://api.zevpaycheckout.com/v1/checkout/static-account",
{
method: "POST",
headers: {
"Content-Type": "application/json",
"x-api-key": "sk_live_your_secret_key",
},
body: JSON.stringify({
customer_name: "Adaeze Okafor",
identity_type: "bvn",
identity_number: "22345678901",
customer_email: "adaeze@example.com",
reference: "customer-1042",
metadata: { plan: "pro" },
}),
}
);
const { data } = await response.json();Response
{
"success": true,
"data": {
"id": "V1StGXR8_Z5jdHi6B-myT",
"account_number": "6641234567",
"account_name": "Adaeze Okafor",
"bank_name": "PalmPay",
"bank_code": "100039",
"currency": "NGN",
"status": "active",
"customer_name": "Adaeze Okafor",
"customer_email": "adaeze@example.com",
"customer_phone": null,
"identity_type": "bvn",
"identity_number_masked": "*******8901",
"reference": "customer-1042",
"metadata": { "plan": "pro" },
"inflow_count": 0,
"total_received": "0.00",
"last_payment_at": null,
"disabled_at": null,
"deleted_at": null,
"created_at": "2026-09-08T09:12:00.000Z",
"updated_at": "2026-09-08T09:12:00.000Z"
}
}Show the customer account_number and bank_name. The identity number is never returned in full.
Account object
| Field | Type | Description |
|---|---|---|
id | string | Account id. Use it in every other call |
account_number | string | The NUBAN the customer transfers to |
account_name | string | Name on the account |
bank_name | string | Bank the customer selects when transferring |
bank_code | string | null | NIP bank code from the live bank list, when known. Show bank_name to customers; codes differ between bank apps |
status | string | "active", "disabled" (paused by you), "suspended" (by ZevPay), "deleted" |
identity_type | string | "bvn", "nin" or "rc" |
identity_number_masked | string | Last four characters only |
reference | string | null | Your reference |
inflow_count | integer | Payments received so far |
total_received | string | Gross amount received so far, in naira |
last_payment_at | string | null | ISO 8601 timestamp of the latest payment |
List accounts
GET /v1/checkout/static-account| Parameter | Type | Default | Description |
|---|---|---|---|
page | integer | 1 | Page number |
page_size | integer | 20 | Items per page (max 100) |
status | string | none | "active", "disabled", "suspended" or "deleted" |
search | string | none | Matches account number, customer name, account name, reference or email |
Returns { items, total, page, page_size, total_pages } with account objects in items.
Get an account
GET /v1/checkout/static-account/{id}Returns the account object.
List payments on an account
GET /v1/checkout/static-account/{id}/payments| Parameter | Type | Default | Description |
|---|---|---|---|
page | integer | 1 | Page number |
page_size | integer | 20 | Items per page (max 100) |
{
"success": true,
"data": {
"items": [
{
"id": "9f1c1e2a-8c1d-4d5c-9a44-1e0b2f6d7a10",
"static_account_id": "V1StGXR8_Z5jdHi6B-myT",
"amount": "25000.00",
"fee": "375.00",
"net_amount": "24625.00",
"currency": "NGN",
"status": "completed",
"payer": { "name": "ADAEZE OKAFOR", "bank": "GTBank", "account": "0123456789" },
"narration": "Top up",
"session_id": "000013250908091200000123456789",
"received_at": "2026-09-08T09:12:44.000Z",
"booked_at": "2026-09-08T09:12:45.000Z",
"settlement": {
"status": "pending_settlement",
"expected_payout_at": "2026-09-09T16:00:00.000Z",
"settled_at": null,
"payout_id": null
},
"refundable": true,
"refund": null
}
],
"total": 1,
"page": 1,
"page_size": 20,
"total_pages": 1
}
}status is "completed" once the payment is booked; "processing" is transient while a payment is being booked.
settlement.status follows the payment through settlement: pending_settlement (waiting for the next cutoff; expected_payout_at is the scheduled payout), batched (in a payout run), paid_out or wallet_credit (settled to you, settled_at set), under_review (held because a refund was requested), refunded, or failed (payout failed; support will retry). refundable is true while a refund can still be requested.
Pause and resume
POST /v1/checkout/static-account/{id}/disable
POST /v1/checkout/static-account/{id}/enablePausing sets status to "disabled"; the bank rejects transfers until you resume. Both return the account object. You cannot resume an account that ZevPay has suspended.
Delete an account
DELETE /v1/checkout/static-account/{id}Retires the account number permanently (status: "deleted"). This cannot be undone and the number is not reused. Payment history stays available. Returns the account object.
Refunds
A payment can be refunded to the payer as long as it has not yet been settled to you: the request is accepted only while the payment is still pending settlement (before the next payout batch picks it up). Refunds are two-step: your request holds the payment out of settlement and hands it to ZevPay; ZevPay then returns the money to the payer's bank account and fires charge.refunded. If ZevPay declines the request, the payment goes back into your next settlement and the refund shows as rejected.
Request a refund
POST /v1/checkout/static-account/{id}/payments/{payment_id}/refund| Parameter | Type | Required | Description |
|---|---|---|---|
reason | string | No | Why you are refunding (max 500 chars). Shown to ZevPay's operations team |
payment_id is the id from the payments list, where refundable: true tells you the payment can still be refunded. Repeating the request for the same payment returns the open refund.
{
"success": true,
"data": {
"id": "V1StGXR8_Z5jdHi6B-myT",
"amount": "25000.00",
"currency": "NGN",
"status": "requested",
"reason": "Duplicate payment",
"rejection_reason": null,
"failure_reason": null,
"payment_id": "9f1c1e2a-8c1d-4d5c-9a44-1e0b2f6d7a10",
"requested_at": "2026-09-08T10:02:11.000Z",
"completed_at": null
}
}status moves requested → processing → completed (money returned) or failed; rejected means ZevPay declined and the payment settles to you as normal.
List and get refunds
GET /v1/checkout/static-account/refunds?status=requested&page=1&page_size=20
GET /v1/checkout/static-account/refunds/{refund_id}Both return refund objects in the shape above.
| Code | HTTP | Meaning |
|---|---|---|
REFUND_NOT_ALLOWED | 400 | The payment is not booked, or is already in a settlement batch or paid out |
ALREADY_REFUNDED | 400 | This payment was refunded already |
Webhook
Every payment into a static account fires charge.success to the webhook URL of the API key that owns the account, with channel: "static_account" and a static_account object identifying which account was paid. See the payload.
Errors
| Code | HTTP | Meaning |
|---|---|---|
STATIC_ACCOUNT_NOT_ENABLED | 403 | Static NGN Accounts are not enabled for this merchant account |
STATIC_ACCOUNT_SUSPENDED | 403 | Access is suspended: existing accounts and history stay visible on the dashboard, but every API call and every action is refused until it is lifted |
IDENTITY_MISMATCH | 400 | customer_name does not match the identity record for identity_number |
IDENTITY_CHECK_UNAVAILABLE | 503 | The identity registry could not be reached; retry shortly |
STATIC_ACCOUNT_REJECTED | 400 | The bank declined to issue the account for the details given |
STATIC_ACCOUNT_UNAVAILABLE | 503 | Account issuing is temporarily unavailable; retry later |
REFERENCE_IN_USE | 409 | reference belongs to a deleted account; use a new reference |
ACCOUNT_SUSPENDED | 400 | The account is suspended by ZevPay and cannot be changed |
ACCOUNT_DELETED | 400 | The account has been deleted |
STATIC_ACCOUNT_RATE_LIMITED | 429 | Too many create attempts, or too many identity mismatches, in the last hour for your account |
VALIDATION_ERROR | 400 | A field has the right shape but an invalid value (message says which). Missing or wrongly typed fields return the standard HTTP_ERROR envelope with a list of messages |