Appearance
Core flows
Which calls make up each job. Every write returns the same operation object; poll GET /operations/{uid} or wait for the webhook.
uid · type · state · amount · fee · tax · currency · clientReference · linksEvery money field is a decimal string with two places ("2500.00") — see Amounts.
Onboard a customer
| Step | Call | Scope |
|---|---|---|
| 1 | POST /customers — creates the customer and main wallet | customers:write |
| 2 | GET /customers/{uid} — status, KYC, wallets | customers:read |
Webhook: customer.created, later customer.kyc_changed. Personal data in responses is always masked; the summary tells you which identifier a payout may go to (payoutEligible).
Send only what we cannot work out. Each identifier needs its type and value; the class, primary flag and verification status follow from the type unless you say otherwise. kyc needs the level, the risk rating and your evidence. agreements must carry the customer's acceptance of the wallet terms, privacy notice and fee schedule — one entry each with agreementTypewallet_opening, privacy and fee_schedule, captured in your own channel. A request missing any of them is refused with 422 RequiredAgreementsMissing and nothing is created; there is no partial or pending customer.
clientReference is your own id for the customer. It is unique among your customers, comes back on every customer response and on the customer.created webhook, and the client portal finds a customer by it.
A 409 on POST /customers means your account already has a customer with that verified identifier, or with that clientReference.
Choosing a wallet profile
A wallet profile decides what a customer's wallet can do — for example, whether it can pay out to a payment provider, or only be topped up and pay you. Your account is approved for one or more profiles, and has a default among them.
- Omit
rulesProfileKeyand the wallet opens on your default profile. - Send
rulesProfileKeyto choose another profile your account is approved for. - The response, and
GET /customers/{uid}, return therulesProfileKeythe wallet opened on.
The same approval covers the KYC level you assert (kyc.tierCode, and a later KYC level change) and the payment provider you name (railCode) on top-up intents and payouts. A profile, KYC level or payment provider outside your approval is refused with 422. Contact us to change what your account is approved for.
Credit a wallet
| Step | Call | Scope |
|---|---|---|
| 1 | POST /wallet-credits | wallet-credits:write |
Synchronous: the 201 is already completed. Funds come from your prefunded balance with Neo Wallet — if it cannot cover the credit you get 422 FundingNotPositioned. Webhook: wallet_credit.completed.
Debit a wallet
Debits need the customer's consent first, as a mandate.
| Step | Call | Scope |
|---|---|---|
| 1 | POST /mandates with an assertion | mandates:write |
| 2a | Customer present: POST /wallet-debits with an assertion | wallet-debits:write |
| 2b | Customer absent: POST /mandates/{uid}/debits — no assertion | wallet-debits:write |
Webhooks: mandate.created, wallet_debit.completed, wallet_debit.failed.
Transfer between wallets
A customer sends money to another of your customers, or pays your business collection wallet.
| Step | Call | Scope |
|---|---|---|
| 1 | POST /wallet-transfers with the sender's assertion | wallet-transfers:write |
Webhooks: transfer.completed. Details: Wallet transfers.
Pay out
| Step | Call | Scope |
|---|---|---|
| 1 | Optional: POST /fees/quote — show the customer the total | wallets:read |
| 2 | POST /payouts with an assertion (and feeQuoteId) | payouts:write |
| 3 | Wait for the webhook | — |
Asynchronous. The 202 is processing: the wallet is already debited and the money is on its way. Then exactly one of:
| Webhook | Meaning |
|---|---|
payout.confirmed | Delivered. |
payout.failed | Rejected by the payment provider; amount and refundable charges are back in the wallet. |
payout.uncertain | The provider has not answered. Do not resend — we resolve it and notify you. |
The destination must be the customer's own verified contact identifier.
Top up from outside
| Step | Call | Scope |
|---|---|---|
| 1 | POST /topup-intents → intentReference, expiresAt | topup-intents:write |
| 2 | We send a payment prompt for the exact amount to the customer's phone; they approve it there | — |
| 3 | Webhook topup.confirmed — or topup.expired if the customer did not approve in time | — |
The prompt goes to the customer's verified phone number: their primary one, or, if the primary contact is not a verified phone number, the verified phone number added first. A customer without one is refused with 422. To prompt another of the customer's verified phone numbers for one top-up, send its uid (identifiers[].uid on GET /customers/{uid}) as payerIdentifierUid; a uid that is not one of the customer's verified phone numbers is refused with 422, and the customer's primary number stays as it is. Show your customer payerInstructions. A prompt the customer declines or ignores is not retried: the intent expires, and you create a new one to try again.
Read
| Want | Call |
|---|---|
| Balance | GET /wallets/{uid}/balance |
| One operation + timeline | GET /operations/{uid} |
| Customer-facing receipt | GET /operations/{uid}/receipt |