Bank Transfers
Bank transfers send money from a PayKore wallet to an external Nigerian bank account, via the NIP (NIBSS Instant Payment) banking rails through PayKore's PSP partners.
Unlike P2P transfers, bank transfers are asynchronous. You receive a 202 Accepted immediately, then a webhook when the transfer confirms — typically seconds to minutes later.
Fee: ₦50 flat (5000 kobo) per transfer.
Verify the bank account first
Before initiating a transfer, verify the account number resolves to a real account. This prevents sending money to a mistyped account number — funds sent to the wrong NUBAN are very difficult to recover.
curl "https://api.paykore.dev/v1/banks/resolve?bank_code=058&account_number=0123456789" \
-H "X-API-Key: sk_test_YOUR_KEY_HERE"
Response:
{
"account_number": "0123456789",
"account_name": "ADEBAYO OLUWASEUN JAMES",
"bank_code": "058",
"bank_name": "Guaranty Trust Bank",
"meta": {
"request_id": "req_3pQrS4tUvW"
}
}
Display account_name to your user and ask them to confirm it matches the intended recipient before submitting the transfer.
Invalid account number:
{
"error": {
"code": "ACCOUNT_RESOLUTION_FAILED",
"message": "Could not resolve this account number with the specified bank.",
"http_status": 422
},
"meta": {
"request_id": "req_8rStU9vWxY"
}
}
Always resolve the account and show the account name for user confirmation before submitting a bank transfer. PayKore cannot reverse a transfer sent to a valid but unintended account.
Initiating the transfer
curl -X POST https://api.paykore.dev/v1/transfers/bank \
-H "X-API-Key: sk_test_YOUR_KEY_HERE" \
-H "Idempotency-Key: payout_20260602_001" \
-H "Content-Type: application/json" \
-d '{
"source_wallet_id": "wlt_9f3kA2mXpQ",
"dest_account": {
"bank_code": "058",
"account_number": "0123456789",
"account_name": "ADEBAYO OLUWASEUN JAMES"
},
"amount_kobo": 1000000,
"reference": "payout_456",
"description": "Withdrawal to bank"
}'
Response — 202 Accepted:
{
"id": "tx_8wXyZ1aBcD",
"type": "bank_transfer",
"status": "processing",
"reference": "payout_456",
"amount_kobo": 1000000,
"amount_naira": "10000.00",
"fee_breakdown": {
"customer_fee_kobo": 5000,
"platform_fee_kobo": 1000,
"mfb_cost_kobo": 4000
},
"dest_account": {
"bank_code": "058",
"bank_name": "Guaranty Trust Bank",
"account_number": "0123456789",
"account_name": "ADEBAYO OLUWASEUN JAMES"
},
"created_at": "2025-06-01T11:00:00Z",
"meta": {
"request_id": "req_4tUvW5xYzA"
}
}
Key point: the source wallet is debited immediately upon acceptance (you can confirm via source_wallet.balance_after_kobo if included, or a follow-up GET). The actual bank payout is in flight — status: "processing" means PayKore has accepted the request and is routing it through the PSP. It is not yet confirmed by the receiving bank.
The async lifecycle
Typical timing:
| Stage | Duration |
|---|---|
202 response to your server | Under 1 second |
| PSP payout during banking hours | 10–30 seconds typically |
| PSP payout outside banking hours / weekends / public holidays | Up to 24 hours |
Design your UI to reflect "processing" rather than assuming completion at the 202 response. Show a pending state to your user until the webhook confirms.
Handling the webhook
On success — transaction.completed:
{
"event": "transaction.completed",
"data": {
"id": "tx_8wXyZ1aBcD",
"type": "bank_transfer",
"status": "completed",
"reference": "payout_456",
"amount_kobo": 1000000,
"psp_reference": "FLW-PO-9928374651",
"completed_at": "2025-06-01T11:00:24Z"
},
"timestamp": "2025-06-01T11:00:25Z",
"webhook_id": "wh_2cDeF3gHiJ"
}
On failure — transaction.failed:
{
"event": "transaction.failed",
"data": {
"id": "tx_8wXyZ1aBcD",
"type": "bank_transfer",
"status": "failed",
"reference": "payout_456",
"amount_kobo": 1000000,
"failure_reason": "Beneficiary bank rejected: account dormant",
"reversal": {
"reversed": true,
"reversal_tx_id": "tx_9xYzA2bCdE",
"amount_kobo": 1000000
}
},
"timestamp": "2025-06-01T11:01:10Z",
"webhook_id": "wh_5fGhI6jKlM"
}
On failure, the source wallet is automatically credited back the full debited amount (including fees) via the reversal object. You don't need to issue a manual refund — just update your records to reflect the failed state.
Reconciliation pattern:
async function handleWebhook(event: WebhookEvent) {
const { id: transactionId, status, reference } = event.data;
const payout = await db.payouts.findByReference(reference);
if (!payout) {
logger.warn('Webhook for unknown reference', { reference });
return;
}
await db.payouts.update(payout.id, {
status,
paykoreTransactionId: transactionId,
completedAt: status === 'completed' ? new Date() : null,
});
}
Polling as a fallback
If your webhook endpoint was temporarily down, you can poll for the current status using your reference:
curl "https://api.paykore.dev/v1/wallets/wlt_9f3kA2mXpQ/transactions?reference=payout_456" \
-H "X-API-Key: sk_test_YOUR_KEY_HERE"
Implement webhooks as your primary mechanism for status updates. Use polling only as a fallback — for example, a scheduled job that checks any processing transfers older than 5 minutes, in case a webhook delivery was missed.
Bank codes reference
| Bank | Code |
|---|---|
| Guaranty Trust Bank (GTBank) | 058 |
| Access Bank | 044 |
| Zenith Bank | 057 |
| First Bank of Nigeria | 011 |
| United Bank for Africa (UBA) | 033 |
| First City Monument Bank (FCMB) | 214 |
| Sterling Bank | 232 |
| Wema Bank | 035 |
| Fidelity Bank | 070 |
| Union Bank | 032 |
This is a partial list of major banks. The full, up-to-date list is available via:
curl https://api.paykore.dev/v1/banks \
-H "X-API-Key: sk_test_YOUR_KEY_HERE"
See the API Reference for the complete endpoint schema.
Sandbox behaviour
In sandbox, bank transfers complete immediately — within 1–2 seconds — using a fake PSP reference. The transaction.completed webhook fires automatically with no real money moving. This lets you build and fully test your webhook handler without waiting on real banking rails.
Sandbox does not simulate failures by default. To test your transaction.failed handling path, use a destination account number ending in 0000 — this is a reserved sandbox pattern that always triggers a simulated failure with reversal.
Next steps
- Webhooks Overview → — Full webhook delivery, retries, and signature verification.
- P2P Transfers → — The synchronous alternative for wallet-to-wallet transfers.
- Sandbox & Testing → — More sandbox-only behaviours and test data.