NGN payouts
Send NGN to any Nigerian bank account. Routed over the Nigerian interbank rails (NIP) through a multi-provider cascade — if one provider can’t reach a bank, Swappr automatically tries the next. Recipient names are auto-resolved from the account number, so you only send account_number + bank_code.
New to payouts? Start with the Payouts overview for the common fields, customer attribution, and management endpoints shared across every currency.
Want the exact fee before you send? Preview it with POST /v1/payouts/quote — an advisory, read-only fee + cost breakdown for your account.
The payout object
{
"object": "payout",
"id": "ckxxxxxxxxxxxxxxxxxx",
"reference": "po_xxxxxxxxxxxx",
"status": "paid",
"currency": "NGN",
"amount_minor": "500000",
"fee_minor": "75",
"tax_minor": "0",
"total_debit_minor": "500075",
"recipient_name": "ADAEZE BLESSING NWAFOR",
"recipient_account": "0690000032",
"recipient_bank_code": "044",
"wallet_id": "ckwallet_xxx",
"provider": "<provider_slug>",
"provider_ref": "<provider_ref>",
"nip_reference": "100004250505000123456789",
"merchant_reference": "ORDER_001",
"narration": "Payroll April 2026",
"customer_id": null,
"customer_reference": null,
"batch_id": null,
"failure_code": null,
"failure_message": null,
"created_at": "2026-05-05T12:34:50.123Z",
"queued_at": "2026-05-05T12:34:50.456Z",
"processing_at": "2026-05-05T12:34:51.789Z",
"completed_at": "2026-05-05T12:34:56.789Z"
}Status values
| Status | Description |
|---|---|
draft | Created via dual-control flow; awaiting team approval |
queued | Approved/auto-approved; awaiting dispatch |
processing | Dispatched to provider; awaiting confirmation |
paid | Funds reached the recipient |
paid_manual | Manually marked paid by Technest support after evidence review |
failed | Provider returned failed; if applicable, wallet was auto-reversed |
failed_manual | Manually marked failed by Technest support |
reversed | Paid payout reversed (refund issued) |
cancelled | Cancelled before dispatch |
awaiting_admin_review | Held by an internal fraud rule; awaiting Technest review |
Maker-checker rules
Single payouts that exceed your account’s per-transaction dual-control threshold land in draft status awaiting team approval (approve via the dashboard or your internal tooling that calls our dashboard API).
Live-merchant Owner-only self-approve: on live-approved merchants, only the team member assigned the Owner role can approve their own draft payouts. All other roles drop to maker-checker on live and need a different teammate to approve, even when they hold both payout_create and payout_approve. Sandbox keeps dual-perm self-approve for development velocity. The Owner role is capped at 3 holders per merchant.
Email-OTP gate (dashboard only)
The dashboard’s New Payout and Approve flows can require an email-OTP before submit and/or approve, configured per (merchant, env, currency).
API integrations bypass the OTP gate entirely — POST /v1/payouts authenticates via Bearer token + IP allowlist + Idempotency-Key, which is the right machine-to-machine shape. The OTP is a dashboard-only control for human-clicked actions.
POSTCreate an NGN payout
Send NGN to a Nigerian bank account. The Idempotency-Key header is required.
Minor units (kobo).
NGN
Inline recipient (below). Required unless beneficiary_id is supplied.
A saved NGN beneficiary. Alternative to inline recipient.
Auto-resolved from currency + env if omitted.
Your own ref. Unique per merchant in a 30-day window.
Shown on the recipient’s bank statement.
Sender/customer attribution — see Customer attribution.
Opaque attribution string (max 64 chars).
Bypass the beneficiary cool-down for an intentional retry.
NGN does not use sender / sender_customer_id (those are for FX currencies).
Inline recipient
{
"account_number": "0690000032",
"bank_code": "044"
}| Field | Required | Notes |
|---|---|---|
account_number | yes | 10-digit NUBAN. |
bank_code | yes | Any code from GET /v1/banks?currency=NGN — validated against the bank list, not a length rule. Real NGN codes come in several widths: CBN 3-digit (058 for GTBank), NIBSS/NIP 5-digit (50515 for Moniepoint) and 6-digit (000013 for GTBank). Send whichever the bank publishes; Swappr canonicalizes it internally. |
bank_name | no | Optional hint. If omitted, Swappr resolves the canonical name and stores it for dashboards + receipts. |
name | no | Optional. Auto-resolved via NUBAN against the bank-of-record. |
Swappr canonicalizes whichever code form you send to the 3-digit CBN form internally before dispatch. A bank_code that no bank in the list carries returns 422 unknown_bank_code with the input echoed; a non-numeric bank_code returns 400 invalid_field.
Tip: call GET /v1/banks?currency=NGN once at integration time, cache the response (it returns both cbn_code and nip_code per bank), and look up by name when building payloads. Daily refresh is plenty.
Errors
| Code | HTTP | Cause |
|---|---|---|
missing_field | 400 | Required body field absent |
invalid_field | 400 | Wrong type or format of a body field (e.g. a non-numeric bank_code) |
unknown_bank_code | 422 | bank_code not found in the bank list |
wallet_not_found | 422 | wallet_id doesn’t match merchant + currency + env |
recipient_unresolvable | 422 | NUBAN cascade exhausted; account invalid at every provider |
beneficiary_cooldown | 429 | Same recipient paid recently — pass allow_duplicate: true if intentional |
limit_violation | 422 | Per-tx / daily / per-beneficiary cap exceeded; see error.detail.kind |
fraud_rule_blocked | 422 | Internal fraud rule blocked the payout; see error.detail.hits |
beneficiary_blacklisted | 403 | Recipient is on your merchant blacklist — un-blacklist them on the Beneficiaries page, or send to a different recipient |
beneficiary_globally_blocked | 403 | Recipient is on the platform-wide blacklist and cannot be overridden — contact support |
wallet_frozen | 403 | The source wallet is frozen; no debits are permitted |
customer_not_found | 404 | customer_id unknown / not yours |
env_mismatch | 409 | customer_id belongs to the other environment |
idempotency_key_conflict | 409 | Same Idempotency-Key used with a different body |
idempotency_conflict | 409 | Same Idempotency-Key as a prior payout that differs in amount or recipient account — narrower than idempotency_key_conflict, which compares the whole request body |
merchant_reference_duplicate | 409 | merchant_reference was already used on a recent payout; each reference is single-use within the cool-down window |
insufficient_funds | 402 | Wallet balance is below the payout amount plus fees |
routing_failed | 422 | No disbursement route is available for this payout, or the destination bank could not be identified |
provider_error | 502 | Downstream rail unreachable; safe to retry with the same key |
curl https://api.swappr.me/api/v1/payouts \
-H "Authorization: Bearer sk_test_..." \
-H "Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" \
-d '{
"amount_minor": "500000",
"currency": "NGN",
"recipient": {
"account_number": "0690000032",
"bank_code": "044"
},
"merchant_reference": "ORDER_001",
"narration": "Payroll April 2026",
"customer_id": "cust_cmoji8..."
}'curl https://api.swappr.me/api/v1/payouts \
-H "Authorization: Bearer sk_test_..." \
-H "Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" \
-d '{
"amount_minor": "500000",
"currency": "NGN",
"beneficiary_id": "ben_cmo8x2p9q0...",
"merchant_reference": "ORDER_002"
}'Response
201 Created with the payout object. Status is typically paid in sandbox, or queued / processing in live (transitions to paid when the provider confirms — listen for the payout.paid webhook).
Webhook events
NGN payouts emit payout.processing, then payout.paid or payout.failed (and payout.reversed if a paid payout is later reversed). If you set customer_id / customer_reference, they’re included in the event payload. See Webhooks.
Fund from another currency (cross-currency)
You can fund an NGN payout from a different wallet currency (e.g. pay ₦ from your CAD wallet) in a single call by adding funding_currency to the request. See Cross-currency payouts for the full method, both amount options (destination / source), the fx response block, fee_inclusive, rate-freshness behaviour, and the error table.
Manage payouts
These endpoints are currency-agnostic — they work the same for NGN and every FX currency.
GETList payouts
Cursor-paginated. Filter by status, currency, created_after, created_before, limit (1–100, default 50), starting_after.
{
"object": "list",
"has_more": true,
"data": [{ "object": "payout", "id": "...", "...": "..." }]
}GETRetrieve a payout
Accepts the cuid (ckxxx) OR the po_xxx reference.
curl https://api.swappr.me/api/v1/payouts/po_da06226542dc44a9 \
-H "Authorization: Bearer sk_test_..."POSTRe-query payout status
Forces a status refresh by calling the provider directly. Use it when a payout has been stuck in processing unusually long. If the provider returns failed, the wallet is auto-reversed (principal + fee + tax credited back).
POSTCancel a payout
Cancel a payout in draft (no wallet movement) or queued (wallet auto-reversed) status. Body: { "reason": "..." } (required, 3–500 chars). Cannot cancel processing or terminal-status payouts.