Skip to content

Wallet transfers ​

A wallet transfer moves money from one of your customers' wallets to another wallet of yours, immediately. No payment provider is involved and nothing leaves Neo Wallet.

transferCategoryPaysCounts against
p2pAnother customer's walletThe sender's daily person-to-person limit
merchantYour business collection walletThe sender's daily merchant-payment limit

Both limits belong to the sender's KYC level and apply on top of the sender's general daily and monthly limits.

Before you start ​

  • Your account needs the wallet-transfers:write scope. It is not part of any existing account's grants; ask for it.
  • Both wallets must be on a wallet profile that allows transfers. A wallet's profile is set when you onboard the customer — see Choosing a wallet profile.
  • Both wallets must be yours: wallets of customers you onboarded, or your own business collection wallet. Anything else is a 404, the same as a wallet that does not exist.

Make a transfer ​

POST /wallet-transfers with an Idempotency-Key:

json
{
  "sourceWalletUid": "01J0WALLETSENDER0000000000",
  "destinationWalletUid": "01J0WALLETRECIPIENT0000000",
  "amount": "1000.00",
  "currency": "ETB",
  "transferCategory": "p2p",
  "clientReference": "TRF-2026-000042",
  "assertion": { "...": "the sending customer's authentication" }
}

The assertion is the sending customer's, and its transaction binding uses destinationWalletUid as the target:

canonical = customerUid | sourceWalletUid | amount | currency | destinationWalletUid | clientReference

The customer therefore authenticates who is paid, not only how much: an assertion made for one recipient is refused for another. Above the 2FA threshold of the sender's KYC level, both factors are required.

The answer is 201 with the operation, already completed, carrying sourceWalletUid and destinationWalletUid. fee and tax are what the sender paid on top of the amount; the recipient receives the amount in full.

When it is refused ​

A transfer is all-or-nothing. Each of these is a 422 and nothing moves:

  • either wallet's profile does not allow transfers;
  • merchant names a customer wallet, or p2p names a collection wallet;
  • the two wallets are the same, or not in currency;
  • a limit of the sender's KYC level would be exceeded;
  • the destination wallet cannot take the amount;
  • the sender's available balance is short of the amount plus any fee;
  • either wallet or customer is blocked.

Every one of these returns the same 422 body, and none of them describes the other customer. A refused transfer can be retried with a new Idempotency-Key once the cause is fixed; retrying with the same key returns the same answer — see Safe retries.

Afterwards ​

  • Webhook — transfer.completed, carrying the same operation as the 201.
  • Both sides — the one operation is the sender's debit and the recipient's credit; your records hold both wallets, so show it on each. There is no statement route: your writes and the webhook feed are the history.
  • Receipts — GET /operations/{uid}/receipt is the sender's receipt, with the fee and tax itemized. Add ?walletUid= with the wallet that was paid for the recipient's receipt, which shows the amount alone.