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 · amountMinor · feeMinor · taxMinor · currency · clientReference · linksOnboard 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 masked unless you hold customers:pii.
A 409 on POST /customers means you already hold a customer with that verified identifier. A person who is also a customer of another business on Neo Wallet is a separate customer with a separate wallet there; you never see that customer, and onboarding the same person with you is not a conflict.
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 and the generic message; the response does not say which ones exist. Ask us if what your account is approved for needs to change.
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.
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 | The customer pays the exact amount through the payment provider, quoting the reference | — |
| 3 | Webhook topup.confirmed — or topup.expired if nothing matched in time | — |
Read
| Want | Call |
|---|---|
| Balance | GET /wallets/{uid}/balance |
| Statement | GET /wallets/{uid}/transactions |
| One operation + timeline | GET /operations/{uid} |
| Customer-facing receipt | GET /operations/{uid}/receipt |