POST
/
transfer

Pay an external bank account from a deposit-eligible wallet.

Selecting the source wallet (walletId)

If your Business owns more than one wallet — typical once you create outlets — pass walletId in the request body to pick which wallet to debit. walletId accepts either:

  • the 10-digit NUBAN of a primary wallet, or
  • a wal_<26-char-ULID> identifier returned in outlet.depositWallet.walletId.

walletId is optional on the v1-static profile and defaults to your primary wallet for backward compatibility, but partners on the v2-hmac profile must send it on every payout. Sending a walletId you do not own returns 401 WALLET_NOT_OWNED. Sending an outlet collection wallet returns 422 WALLET_TYPE_MISMATCH (see below).

Source wallet rules

Source wallet_kindBehaviour
MAIN (primary deposit, outlet deposit)✅ allowed
COLLECTION (outlet collection)🚫 422 WALLET_TYPE_MISMATCH: collection wallets cannot fund external payouts

The check is structural, not balance-based. A collection wallet with a healthy balance still cannot debit out. Move the funds to a deposit wallet via POST /transfer/internal first.

Idempotency

Pass reference (≤ 255 chars) to make the call idempotent on a partner-supplied key. Behaviour:

  • First call: a new transaction is created with customerReference = your reference. The response carries the Kele-side transactionReference.
  • Retry with the same reference for the same wallet: the original transactionReference is returned. No new debit. No new outgoing webhook.

Use this on every retry path you have. The contract is that re-sending the same body is safe.

Status lifecycle

The synchronous response returns transactionStatus = PENDING or PROCESSING. The terminal state — SUCCESS, FAILED, or REVERSED — arrives via:

Don’t ship money to your customer until the transaction is in a terminal state. PROCESSING is not “done.”

Sandbox testing

Three magic destination account numbers in the sandbox environment force specific outcomes:

accountNumberOutcome
9960000000Delayed failure — transactionStatus transitions PENDING → PROCESSING → FAILED via the requery cycle
9960000001Instant failure — same terminal state, faster
any other 10-digit numberSuccess

Use these to exercise your own bankTransfer.failed handler before going live.

Authorizations

x-api-key
string
headerrequired
x-api-secret
string
headerrequired

Body

application/json
amount
integer
required

The amount of money to be paid out.

sender
string
required

The name of the sender initiating the transfer.

accountName
string
required

The name associated with the transfer account.

accountNumber
string
required

The account number for the transfer, must be 10 digits.

bankCode
string
required

The code of the bank associated with the account, must be 3 digits.

walletId
string

Selects which of your wallets to debit. Accepts either the 10-digit NUBAN of a primary wallet or the wal_<26-char-ULID> identifier of an outlet deposit wallet. Optional on the v1-static profile (defaults to the primary wallet); required for partners on the v2-hmac profile that own multiple wallets. Wallets you do not own return 401 WALLET_NOT_OWNED. Collection wallets return 422 WALLET_TYPE_MISMATCH: collection wallets cannot fund external payouts.

Response

200 - application/json
transactionAccount
string

Account number used in the transaction, if available

productType
string

Product type involved in the transaction

amount
string

Amount of the transaction

transactionStatus
string

Status of the transaction

transactionReference
string

Unique transaction reference

transactionType
string

Type of transaction (e.g., DEBIT or CREDIT)

comment
string

Comment on the transaction

sessionId
string

transaction session Id

createdAt
string

Timestamp of when the transaction was created

sender
object