Transfers
Money out of your balance to any Nigerian bank account.
Pay any Nigerian bank account from your balance, such as your users' withdrawals.
- Off by default. Test keys work now. Live transfers are switched on per business by Paymonetra, with a per-transfer and a daily limit; until then
403 transfers_not_enabled. - Only from your servers. Live calls must come from the server IPs listed on the dashboard (Developers, API transfers), otherwise
403 ip_not_allowed. - Fee on top. The recipient gets
amount; your balance falls bytotal. Transfers use thereadyfigure fromGET /balance. - Sender name. The recipient's bank shows your registered business name, which for a sole trader is usually the owner's name. Put your brand in
narration.
#The order to call them in
GET /banksfor the code. Do not keep your own list.POST /transfers/resolveto see whose account it is, and show that name to your user.POST /transferswith your ownreference, and passaccount_nameso a wrong number is refused rather than paid.- Take the webhook, or poll
GET /transfers/{reference}.
#List banks
{ "banks": [ { "bank_code": "000013", "bank_name": "Guaranty Trust Bank" } ] }Use these codes, not your own list.
#Resolve an account
Look up an account's name before paying it.
{ "bank_code": "000013", "account_number": "0123456789" }{ "account_name": "ADEBAYO OLUWASEUN", "account_number": "0123456789",
"bank_code": "000013", "bank_name": "Guaranty Trust Bank" }422 account_not_found if the bank does not recognise it. Live keys need transfers switched on and a listed IP, as for sending.
#Send a transfer
{
"reference": "wd_8f2c91",
"amount": 25000,
"bank_code": "000013",
"account_number": "0123456789",
"account_name": "Adebayo Oluwaseun",
"narration": "Wallet withdrawal"
}| field | required | notes |
|---|---|---|
reference | yes | your unique id, up to 100 letters, digits, . - _. Reusing it returns the first transfer. |
amount | yes | naira, minimum 100 |
bank_code | yes | from GET /banks |
account_number | yes | ten digits |
account_name | no | the name you expect. If it does not match the bank's, nothing is sent (name_mismatch). Recommended. |
narration | no | up to 100 characters, on the recipient's statement |
Answers 201 with the transfer, or 200 with the existing one when the reference was used before:
{
"reference": "wd_8f2c91",
"transfer_reference": "PMT-3F9A1C22B7D0",
"status": "success",
"amount": 25000, "fee": 50, "stamp_duty": 50, "total": 25100, "currency": "NGN",
"recipient": { "account_name": "ADEBAYO OLUWASEUN", "account_number": "0123456789",
"bank_code": "000013", "bank_name": "Guaranty Trust Bank" },
"narration": "Wallet withdrawal",
"reason": null,
"source": "api",
"mode": "live",
"created_at": "2026-09-28T12:30:00+01:00",
"completed_at": "2026-09-28T12:30:04+01:00",
"reversed_at": null
}status, the one field to branch on:
| status | means | what to do |
|---|---|---|
success | it arrived | mark your withdrawal paid |
processing | sent, not yet confirmed by the bank | wait for the webhook or poll. Do not resend under a new reference. |
failed | it did not go; reason says why | the money is back in your balance; retry with a new reference |
reversed | it arrived, then the receiving bank sent it back | the whole total is back in your balance |
A transfer we accepted that then failed still returns 201 with status: "failed". A 4xx means nothing was created:
| code | http | means |
|---|---|---|
invalid_request | 422 | field names the problem |
name_mismatch | 422 | the account belongs to somebody else; the message names them |
account_not_found | 422 | the bank does not recognise the account |
amount_over_limit | 422 | above your per-transfer limit |
daily_limit | 422 | today's API transfers, fees included, would pass your daily limit; carries remaining_today |
insufficient_ready | 422 | your ready balance does not cover total |
transfers_not_enabled | 403 | live transfers are not switched on for this business |
ip_not_allowed | 403 | the call did not come from a listed server |
provider_error | 502 | we could not reach the bank; nothing was sent, retry the same reference |
Test mode. Transfers succeed, except to account 0000000000, which fails.
#Fetch transfers
By your reference or our transfer_reference. The list is newest first, limit (up to 100) and offset.
#Webhooks
transfer.success, transfer.failed and transfer.reversed, to your webhook URL, signed like every other event, with the transfer above as data. Each is sent once per transfer. Transfers made on the dashboard send them too.
Signed like every other event. The recipe is on webhooks.