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

StatusDescription
draftCreated via dual-control flow; awaiting team approval
queuedApproved/auto-approved; awaiting dispatch
processingDispatched to provider; awaiting confirmation
paidFunds reached the recipient
paid_manualManually marked paid by Technest support after evidence review
failedProvider returned failed; if applicable, wallet was auto-reversed
failed_manualManually marked failed by Technest support
reversedPaid payout reversed (refund issued)
cancelledCancelled before dispatch
awaiting_admin_reviewHeld 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 entirelyPOST /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

POST/v1/payouts

Send NGN to a Nigerian bank account. The Idempotency-Key header is required.

Body parameters
amount_minorstring (BigInt)Required

Minor units (kobo).

currencystringRequired

NGN

recipientobjectConditional

Inline recipient (below). Required unless beneficiary_id is supplied.

beneficiary_idstringConditional

A saved NGN beneficiary. Alternative to inline recipient.

wallet_idstring

Auto-resolved from currency + env if omitted.

merchant_referencestring

Your own ref. Unique per merchant in a 30-day window.

narrationstring

Shown on the recipient’s bank statement.

customer_idstring

Sender/customer attribution — see Customer attribution.

customer_referencestring

Opaque attribution string (max 64 chars).

allow_duplicateboolean

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"
}
FieldRequiredNotes
account_numberyes10-digit NUBAN.
bank_codeyesAny 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_namenoOptional hint. If omitted, Swappr resolves the canonical name and stores it for dashboards + receipts.
namenoOptional. 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

CodeHTTPCause
missing_field400Required body field absent
invalid_field400Wrong type or format of a body field (e.g. a non-numeric bank_code)
unknown_bank_code422bank_code not found in the bank list
wallet_not_found422wallet_id doesn’t match merchant + currency + env
recipient_unresolvable422NUBAN cascade exhausted; account invalid at every provider
beneficiary_cooldown429Same recipient paid recently — pass allow_duplicate: true if intentional
limit_violation422Per-tx / daily / per-beneficiary cap exceeded; see error.detail.kind
fraud_rule_blocked422Internal fraud rule blocked the payout; see error.detail.hits
beneficiary_blacklisted403Recipient is on your merchant blacklist — un-blacklist them on the Beneficiaries page, or send to a different recipient
beneficiary_globally_blocked403Recipient is on the platform-wide blacklist and cannot be overridden — contact support
wallet_frozen403The source wallet is frozen; no debits are permitted
customer_not_found404customer_id unknown / not yours
env_mismatch409customer_id belongs to the other environment
idempotency_key_conflict409Same Idempotency-Key used with a different body
idempotency_conflict409Same 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_duplicate409merchant_reference was already used on a recent payout; each reference is single-use within the cool-down window
insufficient_funds402Wallet balance is below the payout amount plus fees
routing_failed422No disbursement route is available for this payout, or the destination bank could not be identified
provider_error502Downstream rail unreachable; safe to retry with the same key
Request
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..."
  }'
Request — by beneficiary
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

GET/v1/payouts

Cursor-paginated. Filter by status, currency, created_after, created_before, limit (1–100, default 50), starting_after.

Response
{
  "object": "list",
  "has_more": true,
  "data": [{ "object": "payout", "id": "...", "...": "..." }]
}
200 OK

GETRetrieve a payout

GET/v1/payouts/{id}

Accepts the cuid (ckxxx) OR the po_xxx reference.

Request
curl https://api.swappr.me/api/v1/payouts/po_da06226542dc44a9 \
  -H "Authorization: Bearer sk_test_..."

POSTRe-query payout status

POST/v1/payouts/{id}/requery

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

POST/v1/payouts/{id}/cancel

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.