Appearance
Wallet transfers
Transfer money from your customer's wallet to any Neo Wallet
POST
/api/v1/tenant/wallet-transfersScope wallet-transfers:write · Write — send Idempotency-Key and X-Request-Id
Moves the amount from your customer's wallet immediately, to the wallet with destinationAccountNumber: any Neo Wallet (p2p) or your business collection wallet (merchant). No payment provider is involved. Confirm the payee first with GET /wallets/lookup. The assertion is the sending customer's, is validated on every call and must be bound to this request, with destinationAccountNumber as the binding target (see the Customer authentication guide); above the 2FA threshold of the sender's KYC level both factors are required. The source wallet must be one you can see — anything else is a 404 — and its profile must allow transfers. An account number that is not 13 digits with a valid check digit is a 400. The transfer is all-or-nothing: a payee that cannot receive it, a limit, a profile or available funds short of the amount plus any fee are a 422 and nothing moves. The response state is terminal immediately and transfer.completed is delivered to your webhook.
Request body
| Field | Type | Required | Description |
|---|---|---|---|
sourceWalletUid | string | yes | The wallet to take the money from; one of your customers' wallets. |
destinationAccountNumber | string | yes | The account number of the wallet to pay: any Neo Wallet for a p2p transfer, your business collection wallet for a merchant one. 13 digits, the last a check digit. |
amount | string | yes | A positive amount with at most two decimal places. |
currency | string | yes | ISO-4217; must equal both wallets' currency. |
transferCategory | enum | yes | p2p pays another customer's wallet; merchant pays a business collection wallet. Each counts against its own daily limit of the sender's KYC level. One of p2p, merchant. |
clientReference | string | yes | Your own reference; stored and returned verbatim, never interpreted; unique within your account. |
assertion | object | yes | The sending customer's authentication for this transfer. |
assertion.customerUid | string | yes | The customer who authenticated. |
assertion.identifierUid | object | no | The customer identifier the customer authenticated with, if known. |
assertion.assuranceLevel | enum | yes | How strongly your channel authenticated the customer. One of single_factor, two_factor, step_up. |
assertion.factor1Type | string | yes | The first factor used, e.g. pin, password, biometric. At most 32 characters. |
assertion.factor2Type | object | no | Required unless assuranceLevel is single_factor. At most 32 characters. |
assertion.authenticatedAt | string | yes | ISO-8601 — when the customer authenticated. |
assertion.expiresAt | string | yes | ISO-8601; by default at most 10 minutes after authenticatedAt. An expired assertion is refused (422). |
assertion.channelCode | string | yes | Your channel the customer authenticated on. At most 40 characters. |
assertion.deviceHash | object | no | SHA-256 hex of the device fingerprint, if your channel has one. |
assertion.sourceReference | string | yes | Opaque channel evidence reference (a session or login id). Presenting one again while it is live is allowed only for the same customer. At most 160 characters. |
assertion.transactionBinding | object | no | Hash your channel computed over the request this assertion authorizes; a mismatch with the request received is refused. |
assertion.transactionBinding.hash | string | yes | SHA-256 hex (64 characters) over the canonical string of this request. |
assertion.transactionBinding.algorithm | enum | yes | One of sha256. |
assertion.transactionBinding.canonical | enum | yes | The field template hashed: the six values joined with |, UTF-8. The fifth is the payout destination, the mandate for a debit, or empty; clientReference is empty when absent. One of `customerUid |
Example (placeholder values)
json
{
"sourceWalletUid": "01J9Z3K5X8QW2R4T6V8Y0B1C3D",
"destinationAccountNumber": "6695731439148",
"amount": "1000.00",
"currency": "ETB",
"transferCategory": "p2p",
"clientReference": "string",
"assertion": {
"customerUid": "01J9Z3K5X8QW2R4T6V8Y0B1C3D",
"assuranceLevel": "single_factor",
"factor1Type": "string",
"authenticatedAt": "2026-09-03T10:15:30.000Z",
"expiresAt": "2026-09-03T10:15:30.000Z",
"channelCode": "string",
"sourceReference": "string"
}
}Response · 201
| Field | Type | Description |
|---|---|---|
uid | string | |
type | string | Operation type, e.g. wallet_credit, wallet_debit, payout_b2c or topup_c2b. |
state | string | Operation state: created, awaiting_authorization, authorized, held, processing, pending_provider, confirmed, failed, reversed or completed. |
amount | string | A decimal amount with two decimal places. |
fee | string | A decimal amount with two decimal places. |
tax | string | A decimal amount with two decimal places. |
currency | string | ISO-4217. |
clientReference | string | null | Your reference, exactly as you supplied it. |
sourceWalletUid | string | null | Wallet transfers only: the wallet the money left. |
destinationAccountNumber | string | null | Wallet transfers only: the account number the money was sent to. |
createdAt | string | |
completedAt | string | null | When the operation first reached a final state. |
links | object | |
links.self | string | API-relative path of this operation (GET /tenant/operations/{uid}). |
links.receipt | string | API-relative path of its receipt (GET /tenant/operations/{uid}/receipt). |
Example (placeholder values)
json
{
"uid": "01J9Z3K5X8QW2R4T6V8Y0B1C3D",
"type": "string",
"state": "string",
"amount": "2500.00",
"fee": "12.50",
"tax": "1.88",
"currency": "ETB",
"clientReference": "string",
"sourceWalletUid": "01J9Z3K5X8QW2R4T6V8Y0B1C3D",
"destinationAccountNumber": "string",
"createdAt": "2026-09-03T10:15:30.000Z",
"completedAt": "2026-09-03T10:15:30.000Z",
"links": {
"self": "string",
"receipt": "string"
}
}Errors
400 401 403 404 409 422 429 500 503 — one body shape for all of them, see Errors.