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.
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 | 3-digit CBN code OR 6-digit NIP code — either form is accepted. CBN (e.g. 058 for GTBank) is shorter and matches public references; NIP (e.g. 000013) is supported for integrators already speaking NIBSS NIP. Look both up via GET /v1/banks?currency=NGN. |
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 form internally before dispatch. Unknown codes return 422 unknown_bank_code with the input echoed; malformed codes (not 3 or 6 digits) return 422 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 | 422 | Wrong type / format (e.g. bank_code not 3 or 6 digits) |
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 | 422 | 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 |
merchant_blocked | 422 | Recipient on your merchant blacklist |
globally_blocked | 422 | Recipient on the platform-wide blacklist (cannot override) |
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 |
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.
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.