POST
/
transfer
/
internal

Move funds between two wallets that both belong to the authenticated Business. The most common use is funding an outlet’s deposit wallet by transferring from the primary deposit wallet — so the outlet can pay customer winnings without each outlet having to be funded by Kele Operations.

Kind matrix

The server enforces what kinds of wallets can move money between each other:

Source kindDestination kindBehaviour
MAINMAIN✅ allowed (e.g. primary deposit → outlet deposit)
COLLECTIONanything🚫 422 WALLET_TYPE_MISMATCH: collection wallets cannot debit on /transfer/internal
anythingCOLLECTION🚫 422 WALLET_TYPE_MISMATCH: collection wallets only credit via the bank rail
same walletsame wallet🚫 422 sourceWalletId and destinationWalletId resolve to the same wallet

Collection wallets are inbound-only by design — the only way to credit them is via the customer-facing virtual account, which is the bank rail. Trying to push funds in via this endpoint is a policy violation, not a transient failure.

Idempotency

Pass reference to make the call idempotent on a partner-supplied key. If a prior internal-transfer DEBIT under the same Business already exists with the same reference, the original transactionReference is returned and no new ledger row is created. Idempotent retries are safe and cheap.

The lookup is per Business, not per wallet: matching is done on (businessUserId, customerReference=reference, transactionType=DEBIT, senderPlatform=destinationPlatform=our bank). Reusing the same reference from a different source wallet under the same Business will return the existing record. To get a brand-new ledger row, use a fresh reference.

Cross-tenant ownership

Either wallet belonging to a different Business is rejected with 422 WALLET_TYPE_MISMATCH/transfer/internal only supports transfers between wallets under the authenticated Business.

Webhook on success

A internalTransfer.successful event is dispatched after the transaction settles. The payload mirrors the synchronous response.

Authorizations

x-api-key
string
headerrequired

Partner public key (pk_live_… or pk_test_…). Required on every request.

x-api-secret
string
headerrequired

Partner secret. Used in the v1-static profile only.

Body

application/json
sourceWalletId
string
required
destinationWalletId
string
required
amount
integer
required

Amount in whole NGN.

comment
string
reference
string

Response

200 - application/json
status
boolean
required

Boolean success flag.

statusCode
integer
required

HTTP status code mirrored in the body for convenience.

data
object

Aligned to the existing public-doc shape at https://docs.getkele.com/api-reference/transfer/process-transfer.

message
string
required