Virtual accounts
Give each customer a bank account number of their own. Whatever they send to it the provider pays to your business; FastPay never holds it.
Static and dynamic accounts#
- A static account belongs to one customer and takes any number of transfers for as long as it is open. A customer has one open static account per provider: opening a second for the same
customer_refis refused with409and the codevirtual_account_exists. Yourcustomer_refis up to 100 letters, digits, dots, underscores, colons or hyphens, so use your own customer id rather than an email address or a name. - A dynamic account takes a single payment and then expires. Set
expires_inin seconds, from 15 minutes to 30 days; it is a day if you leave it out. Sendamount_minorwhen you know what the customer should pay.
Where the money goes#
Every account is opened under one of your business's provider profiles. FastPay never holds the money a customer sends; the provider pays it to you. A profile is one of two kinds:
- Your own provider connection. The account is opened with your business's own credentials at the provider, so what a customer sends lands in your own account there.
- FastPay-managed. The account is opened with FastPay's credentials, under your own merchant account at the provider, and the provider pays what arrives to your settlement account.
If your business has profiles with more than one provider, say which one to use with provider. With only one, it is used.
What each provider needs#
| Provider | Static account needs | Dynamic account needs | Live, your own connection | Live, FastPay-managed |
|---|---|---|---|---|
Kudakuda | Nothing extra | Not offered | Not yet | Not yet |
Monnifymoniepoint | BVN or NIN | Not offered | Not yet | Not yet |
OPayopay | Nothing extra | Not offered | Not yet | Not yet |
PalmPaypalmpay | BVN or NIN | Not offered | Not yet | Not yet |
Safe Havensafe_haven | BVN or NIN and phone number | Not offered | Not yet | Not yet |
A request missing something its provider needs is refused with 422, and fields names what is missing, such as bvn. A request that does not match the API at all, such as one without a customer_ref or with a BVN that is not 11 digits, is refused before that with 400; its message names the field, and it has no fields list.
BVN and NIN#
Send the customer's bvn or nin, 11 digits, when their provider needs one. If you send both, the BVN is used. It is stored encrypted, used only to open the account with the provider, and never returned: not in a response, a webhook, an error or the dashboard. An error about it never repeats what you sent.
Test and live#
A test key opens accounts with the provider's sandbox, and a test profile may only collect. On your own provider connection, a live key opens live accounts with any provider above that offers virtual accounts, because what arrives lands in your own account at that provider. The connection has to be enabled; while it is not, a request is refused with 409 and the code provider_profile_required.
A live account under a FastPay-managed profile needs two things: a provider that can pay a live account to your own settlement account (the FastPay-managed column above), and an active profile that provider has confirmed does. Until both hold, a live request is refused with 409 and the code settlement_destination_unconfirmed, and the message says which is missing.
Open an account#
curl -X POST "https://api.vervapay.com/api/v1/virtual-accounts" \
-H "Authorization: Bearer $FASTPAY_SECRET_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: account-ada-001" \
-d '{
"account_type": "static",
"customer_name": "Ada Obi",
"customer_ref": "cust_1001",
"email": "ada@example.com",
"bvn": "00000000000"
}'When the provider opens the account, it comes back active with the account_number and bank_name to show your customer. If the provider's answer could not be confirmed it comes back pending with no number: do not give it to your customer, and fetch it again later. Asking again for the same customer's static account returns that pending account with 200 rather than a second one, and once the first request has ended FastPay asks the provider for it again. It stays pending, whatever a later ask is told, until the provider opens it, since the first request may already have. If the provider declines to open a new account, the account is failed and the request is refused with 422. If FastPay could not ask the provider at all, the account is failed and the request is refused with 503: try again. A failed account does not stop you opening another for the same customer. Send an idempotency key with every create.
Close an account#
curl -X DELETE "https://api.vervapay.com/api/v1/virtual-accounts/6f1c2a52-5d0b-4f7e-9a51-3c7c2f0d8e11" \
-H "Authorization: Bearer $FASTPAY_SECRET_KEY" \
-H "Idempotency-Key: request-0001"Closing is for good. Nothing is erased: the account, its payments and its number stay on record, and the number is never given to another customer. The account is closed at its bank where the bank can, otherwise on our side. Closing a closed account returns it with 200, so the call is safe to repeat. A key closes only accounts in its own mode. Send an idempotency key, as with every write. You are told by the virtual_account.closed event.
{
"data": {
"id": "0192f3a1-5d0b-7f7e-9a51-3c7c2f0d8e11",
"account_number": "6000000001",
"bank_name": "Moniepoint Microfinance Bank",
"bank_code": "50515",
"account_name": "Ada Obi",
"type": "static",
"status": "closed",
"customer_ref": "cust-001",
"currency": "NGN",
"created_at": "2026-11-09T09:30:00Z"
}
}Errors#
400 validation_failed: the request does not match the API, such as a required field left out or a value in the wrong form; the message names the field.409 provider_profile_required: your business has no active provider profile in this mode, or the provider connection its profile uses is not enabled.409 settlement_destination_unconfirmed: under a FastPay-managed profile, live accounts are not available on that provider yet, or the profile has not been confirmed to pay you.409 virtual_account_exists: the customer already has an open static account with this provider.409 conflict, when closing: the account is still being opened, or was never opened.422 validation_failedwithfields: the chosen provider needs something the request lacks, or does not offer what it asks for.422 validation_failedwithoutfields: the provider declined to open the account.502 provider_unavailable: the provider returned an account FastPay cannot use. No account was opened, and the one started is marked failed, so retry with the same idempotency key.503 provider_unavailable: the provider cannot open accounts right now, or could not be asked to. No account was opened, and any account started is marked failed, so retry later with the same idempotency key.503 provider_unavailable, when closing: the bank could not close the account right now. Nothing changed; try again.