Your validation endpoint
This is the endpoint you host at the band’s validation URL. The bank POSTs to it at step 2 of the sequence, every time a payer’s bank runs a name enquiry on one of your numbers, and relays your answer. Everything under “What you may add” is optional and additive: an endpoint that answers only responseCode keeps working as it does today.
One fact shapes the lifetime rules on this page: the enquiry and the credit are separate messages, and a credit does not always follow an enquiry you saw. An internal transfer inside the bank to your number carries none, and neither does a payment replayed from a NIBSS settlement file.
What you receive
Section titled “What you receive”POST /your/validate/path HTTP/1.1Content-Type: application/jsonContent-Length: 96
{"custid":"1000000042","channel":"mobile","mac":"a94a8fe5ccb19ba61c4c0873d391e987982fbbd3"}| Field | Meaning |
|---|---|
custid |
The 10-digit virtual account being asked about. |
channel |
Always the literal string mobile. |
mac |
HMAC-SHA1(key = your MAC key, message = custid + "mobile"), lowercase hex. Empty if the band has no key. |
Timeouts are 10 seconds to connect, 15 seconds to read. The body is sent with Content-Length, never chunked. Note the enquiry uses HMAC-SHA1; the payment notification uses HMAC-SHA256 with the same key.
What you must return
Section titled “What you must return”A JSON object. The only required field is responseCode.
{ "responseCode": "0000", "responseDesc": "INVOICE 0042" }"0000"means the virtual account is valid and the payment may proceed. The payer’s bank is answered00.- Any other
responseCodedeclines. The payer’s bank is answered07(account not found) and shows its customer a failure before any money moves. - A non-JSON answer, a timeout, a connection error or a non-2xx status is treated as “your service is unavailable”, not as a decline: the payer’s bank is answered
97(system error) and may retry.
responseDesc is used as the beneficiary name shown to the payer if the bank has configured your band to use it. Otherwise the payer sees the band owner’s registered name, whatever you return. The BVN and KYC tier shown to the payer’s bank come from the band’s configuration, not from your answer.
What you may add
Section titled “What you may add”Three optional fields. Send none, some, or all.
{ "responseCode": "0000", "responseDesc": "INVOICE 0042", "beneficiaryAccount": "6020023456", "amount": "5000.00", "enforceAmount": true}| Field | Type | Meaning |
|---|---|---|
beneficiaryAccount |
string, 10 digits | Which of the registered accounts this virtual account settles to. Alias: beneficiaryAccountNumber. |
amount |
string or number, greater than 0 | What you expect this virtual account to be paid. Alias: expectedAmount. |
enforceAmount |
boolean | Whether a payment for anything other than amount should be refused. Also accepts "true", "1", "Y". Ignored unless amount is present. |
Both are permissioned at the bank, not by you
Section titled “Both are permissioned at the bank, not by you”Neither field does anything until the bank turns it on for your band, and your response looks identical either way.
beneficiaryAccountis honoured only if the bank has registered that account against your band. Anything else is ignored and the payment settles to the band’s default account.amountandenforceAmountare honoured only while the bank has enabled partner amounts for your band. Otherwise both are ignored and any amount is accepted.
You cannot tell from the reply whether an instruction was honoured, because the enquiry succeeds either way; a valid enquiry is never failed over an instruction that cannot be acted on. Agree with the bank first, and have them confirm from their side that the band is configured. The bank’s operations screen shows exactly what was recorded for each of your virtual accounts.
How long each field lasts
Section titled “How long each field lasts”These two rules are different on purpose. It is the one surprising thing in this contract.
beneficiaryAccount is sticky. Once you name an account for a virtual account, it stays until you name a different one.
- An enquiry that omits the field changes nothing. Silence is “no opinion”, not “revert”.
- To send a virtual account back to the band’s default, name the default account explicitly.
It works this way because a credit can reach the bank with no enquiry in front of it. If the destination expired or were tied to one enquiry, those payments would quietly settle somewhere else.
amount is per-enquiry. Every successful enquiry replaces what is held. An enquiry that omits amount clears any amount being held, and enforcement stops.
- Sending
amounton one enquiry and not the next means: no longer expecting a specific amount. - There is no expiry and no “consume on payment”. The next enquiry is what changes it.
It works this way because an amount describes the payment in front of you, not the virtual account. A forgotten one would silently refuse every later payment of a different value, and the payer would only ever see a generic failure.
A declined enquiry changes nothing. If you answer anything other than "0000", nothing is recorded: no destination is bound, and an amount already held is not cleared.
What happens at the payment
Section titled “What happens at the payment”enforceAmount is checked when the money arrives, both before NIBSS is acknowledged and again at posting; a name enquiry carries no amount, so there is nothing to check at enquiry time.
When the amount does not match:
| Returned to NIBSS | 13, invalid amount |
| The payer sees | their own bank’s wording for a rejected transfer |
| Money moved | none; refused before settlement, not reversed afterwards |
The comparison is by value, not text: 5000, 5000.00 and 5000.0000 are the same amount. If the bank has also set a fixed or minimum amount on the whole band, that is checked first. A payment must satisfy both.
What is not affected
Section titled “What is not affected”- Fees. The band’s fee is unchanged and still settles to the band’s fee account wherever the payment lands. A destination instruction moves the principal only.
- Your notification. The POST to your notification endpoint has the same shape. It reports the virtual account that was paid, not the bank-side account it settled into.
- Numbering. You continue to allocate numbers exactly as now.
- Reversals. A reversed payment is taken back out of whichever account received it.
Worked examples
Section titled “Worked examples”A. Invoice for a fixed amount, into a named account
{ "responseCode": "0000", "responseDesc": "INVOICE 0042", "beneficiaryAccount": "6020023456", "amount": "5000.00", "enforceAmount": true }Settles into 6020023456. A payment of 4,999.99 is refused.
B. Same invoice, now part-payable
{ "responseCode": "0000", "responseDesc": "INVOICE 0042", "beneficiaryAccount": "6020023456" }The amount is cleared; any amount is now accepted. The destination is unchanged, and would be unchanged even if beneficiaryAccount were omitted too.
C. Route only, any amount, forever
{ "responseCode": "0000", "responseDesc": "ACME COLLECTIONS", "beneficiaryAccount": "6020023456" }The common case for a collections band: each virtual account belongs to one of your accounts, and any amount is welcome.
D. Move a virtual account to a different account
{ "responseCode": "0000", "responseDesc": "INVOICE 0042", "beneficiaryAccount": "6020034567" }From now on it settles into 6020034567. The bank’s screen shows that it moved, and from where.
E. Amount only, no redirect
{ "responseCode": "0000", "responseDesc": "INVOICE 0042", "amount": 5000, "enforceAmount": true }Settles to the band’s default account, but only for exactly 5,000.
Validation rules, in full
Section titled “Validation rules, in full”An instruction that fails any of these is dropped silently: the enquiry still succeeds and the payment still proceeds under the band’s defaults.
| Field | Rule |
|---|---|
beneficiaryAccount |
Exactly 10 digits; surrounding whitespace is trimmed. |
| Registered against your band by the bank, and still registered when the payment arrives. | |
| Able to receive credits: not closed, frozen, dormant, or under a regulatory restriction. | |
amount |
Parses as a decimal number and is greater than zero. |
| A malformed or non-positive value is dropped, not read as zero. | |
enforceAmount |
Only meaningful alongside a valid amount. |
| Only honoured while the bank has enabled partner amounts for your band. |
Both permissions are re-checked at the moment each payment resolves, not only when you sent the instruction. If the bank withdraws an account’s registration, virtual accounts pointing at it fall back to the band’s default from the next payment; nothing you send is needed to make that happen.
Rollout checklist
Section titled “Rollout checklist”-
Agree with the bank which of your accounts you will name, and whether you want amount enforcement at all. Enforcement refuses live customer payments; make it a deliberate choice.
-
The bank registers those accounts against your band and, if wanted, enables partner amounts. Until then everything you send is ignored.
-
Start sending
beneficiaryAccounton a small number of virtual accounts and confirm with the bank that they show the destination you expect, before rolling it out across the band. -
Only then add
amountandenforceAmount, remembering the lifetime rule: once you start sending an amount, every subsequent enquiry for that virtual account must either restate it or deliberately drop it. -
Reconcile against the account each virtual account now settles to, not the band’s old default.
In the portal
Section titled “In the portal”The Virtual accounts page shows your band with its notification URL and settlement account, never the validation URL or the key. The per-number destination and amount the bank currently holds are on the bank’s operations screen; ask your relationship manager to read them back to you when a payment did not land where you expected.