Skip to main content

Transfers & Payouts

Send money to Nigerian bank accounts with single or bulk transfers.


List Banks

Fetch the list of supported banks and their codes.

Headers

HeaderValue
AuthorizationBearer $PAYSCRIBE_API_KEY

Request

GET /payouts/bank/list
curl -X GET https://sandbox.payscribe.ng/api/v1/payouts/bank/list \
-H "Authorization: Bearer $PAYSCRIBE_API_KEY"

Response

Status: 200 OK

{
"status": true,
"description": "Bank lookup successfully.",
"message": {
"details": [
{
"code": "058",
"name": "GTBank"
},
{
"code": "044",
"name": "Access Bank"
},
{
"code": "057",
"name": "Zenith Bank"
},
{
"code": "011",
"name": "First Bank"
},
{
"code": "033",
"name": "United Bank for Africa"
},
{
"code": "032",
"name": "Union Bank"
}
]
},
"status_code": 200
}

Test safely in sandbox

Do not paste an API key into a documentation page or browser. Instead, call this read-only endpoint from your server with a ps_pk_test_... key and inspect the response in your application logs or test suite. Use the Sandbox testing guide to verify the full payout lifecycle, including recipient validation and final reconciliation.

Account Name Lookup

Verify the account name before sending a transfer.

Headers

HeaderValue
AuthorizationBearer $PAYSCRIBE_API_KEY
Content-Typeapplication/json

Body Parameters

FieldTypeRequiredDescription
accountstringYesThe NUBAN account number to look up (10 digits)
bankstringYesThe bank code of the receiving bank

Request

POST /payouts/account/lookup
curl -X POST https://sandbox.payscribe.ng/api/v1/payouts/account/lookup \
-H "Authorization: Bearer $PAYSCRIBE_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"account": "0123456789",
"bank": "058"
}'

Response

Status: 200 OK

{
"status": true,
"description": "Account name retrieved successfully",
"message": {
"details": {
"account_number": "0123456789",
"account_name": "JOHN DOE",
"bank_code": "058"
}
},
"status_code": 200
}

Get Transfer Fee

Calculate the transfer fee for a given amount. amount is in naira (not kobo) — the same unit Single Transfer expects.

Headers

HeaderValue
AuthorizationBearer $PAYSCRIBE_API_KEY

Query Parameters

FieldTypeRequiredDescription
amountnumberYesTransfer amount in naira, minimum 100
currencystringYesCurrency code. Sandbox always prices in NGN regardless of the value sent

Request

GET /payouts/fee
curl -X GET "https://sandbox.payscribe.ng/api/v1/payouts/fee?amount=5000&currency=NGN" \
-H "Authorization: Bearer $PAYSCRIBE_API_KEY"

Response

Status: 200 OK

{
"status": true,
"description": "Transfer fee lookup successful.",
"message": {
"details": {
"amount": "5000",
"currency": "ngn",
"fee": 150
}
},
"status_code": 200
}

There is no total field — add amount and fee yourself to get the debit total. fee is a percentage of amount capped at a fixed maximum, both configured server-side; call this endpoint to get the exact figure rather than hardcoding a rate. Sandbox always prices in ngn (lowercase) regardless of the currency value you send.


Single Transfer

Send money to a single bank account.

Headers

HeaderValue
AuthorizationBearer $PAYSCRIBE_API_KEY
Content-Typeapplication/json

Body Parameters

FieldTypeRequiredDescription
amountnumberYesAmount in naira, not kobo (e.g. 5000 = ₦5,000.00), minimum 100
bank_codestringYesRecipient bank code (bank also accepted)
account_numberstringYesRecipient NUBAN account number, 10 digits (account also accepted)
refstringNoYour transaction reference. Not validated as required, and not auto-generated if omitted — pass one to enable duplicate-transfer protection
narrationstringNoTransfer description; defaults to "Payscribe Payout"

The recipient account name is always resolved server-side via Account Name Lookup — there is no account_name input field; any value sent is ignored.

Request

POST /payouts/transfer
curl -X POST https://sandbox.payscribe.ng/api/v1/payouts/transfer \
-H "Authorization: Bearer $PAYSCRIBE_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"amount": 5000,
"bank_code": "058",
"account_number": "0123456789",
"ref": "ref_abc123",
"narration": "Payment for services"
}'

Response

Status: 200 OK

{
"status": true,
"description": "Transfer Successful.",
"message": {
"details": {
"event_id": "a3f2c9d1-7e4b-4f2a-9c3e-1b8d2f6a0c42",
"event_type": "payouts.created",
"trans_id": "a3f2c9d1-7e4b-4f2a-9c3e-1b8d2f6a0c42",
"session_id": "9c8f3b2a-5d1e-4c7a-9b0f-2e6d4a8c1f30",
"ref": "ref_abc123",
"amount": 5000,
"fee": 150,
"total": 5150,
"beneficiary": {
"bank": "058",
"bank_name": "GTBank",
"account_number": "0123456789",
"account_name": "JOHN DOE"
},
"currency": "",
"narration": "Payment for services",
"status": "success",
"created_at": "2026-07-20T10:30:00.000Z"
}
},
"status_code": 200
}

Sandbox transfers settle immediately with "status": "success" — there is no pending intermediate state in sandbox.

currency is currently always empty

The currency field in this response is empty rather than "NGN" — the request-normalizing layer (Endpoints::bankTransfer()) never forwards a currency key to the transfer processor, only country. All amounts are still NGN in practice (there is no way to select another currency), but the field itself is not populated. Flagging as a code-level gap, not a documentation one.


Bulk Transfer

Send up to 100 transfers in a single request.

Not available in sandbox

Tracing POST /payouts/transfer with more than one rows entry through the live code path shows it cannot succeed against sandbox.payscribe.ng. The request-normalizing bridge (Endpoints::bankTransfer()) puts multiple rows under a rows key with no top-level bank/account/amount, but the sandbox processor it's routed to (SandboxApiEndpoints::bankTransfer()) has no rows-handling branch at all — it always reads flat bank/account/amount fields, finds them empty, and returns a 406 "Bank not found" error. Bulk batching is implemented only in the live processor (App\Libraries\ApiEndpoints::bankTransfer()). Test bulk transfers by calling Single Transfer once per recipient in sandbox; this needs an engineering fix (or an explicit "live only" note) before it can be documented as sandbox-testable.

Headers

HeaderValue
AuthorizationBearer $PAYSCRIBE_API_KEY
Content-Typeapplication/json

Body Parameters

FieldTypeRequiredDescription
refstringNoOne reference applied to the whole batch. Not validated as required, and not auto-generated if omitted
rowsarrayYesArray of transfer objects (max 100)

Transfer object fields:

FieldTypeRequiredDescription
amountnumberYesAmount in naira, not kobo
bank_codestringYesRecipient bank code
account_numberstringYesRecipient NUBAN account number, 10 digits
narrationstringNoPer-transfer description; defaults to "Payscribe Payout"

When rows contains more than one transfer, the request is processed as a batch (live only — see above).

Request

POST /payouts/transfer
curl -X POST https://sandbox.payscribe.ng/api/v1/payouts/transfer \
-H "Authorization: Bearer $PAYSCRIBE_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"ref": "bulk_ref_001",
"rows": [
{
"amount": 5000,
"bank_code": "058",
"account_number": "0123456789",
"narration": "Invoice INV-001"
},
{
"amount": 10000,
"bank_code": "044",
"account_number": "9876543210",
"narration": "Invoice INV-002"
}
]
}'

Response

Status: 200 OK

{
"status": true,
"description": "Bulk transfer queued.",
"message": {
"details": {
"batch_id": "batch_uuid",
"ref": "bulk_ref_001",
"gross": 15000,
"count": 2,
"created_at": "2026-07-20T10:35:00.000Z"
}
},
"status_code": 200
}

Verify Transfer

Check the status of a transfer using the trans_id (also returned as event_id) from its Single Transfer response — not your own ref.

Headers

HeaderValue
AuthorizationBearer $PAYSCRIBE_API_KEY

Path Parameters

FieldTypeRequiredDescription
idstringYesThe trans_id returned by Single Transfer

Request

GET /payouts/verify/{id}
curl -X GET "https://sandbox.payscribe.ng/api/v1/payouts/verify/a3f2c9d1-7e4b-4f2a-9c3e-1b8d2f6a0c42" \
-H "Authorization: Bearer $PAYSCRIBE_API_KEY"

Response

Status: 200 OK

{
"status": true,
"description": "Verification Successful! ",
"message": {
"details": {
"status": "success",
"description": "Money transfer sent to JOHN DOE GTBank ",
"amount": 5000,
"session_id": "9c8f3b2a-5d1e-4c7a-9b0f-2e6d4a8c1f30"
}
},
"status_code": 200
}

This is a much smaller payload than the initial transfer response — it does not repeat the bank/account/fee details, only status, an internal description, amount, and the transfer's session_id. The transaction is looked up by trans_id scoped to your account, so passing your own ref or a bank reference in {id} returns a 404 "Transaction does not exist for this account."


Webhooks

EventDescription
payouts.createdTransfer initiated and pending processing (single transfer or batch)

See Webhooks for payload format and verification.

Was this page helpful?

Report a docs issue →