Wallet
Core Concepts

Core Concepts

Every call in the Wallet API rests on these six concepts. Reading this first makes the field names on later pages self-explanatory.

Program

A program is your wallet product. It defines currencies, branding, fee and limit policy, and which integrations are enabled. One tenant can run several programs (for example "Employee Card" and "Customer Wallet" with different policies).

{
  "name": "Customer Wallet",
  "loopMode": "CLOSED_LOOP",
  "currencies": ["TRY"],
  "branding": {
    "displayName": "Acme Wallet",
    "logoUrl": "https://cdn.acme.com/logo.svg",
    "primaryColor": "#0F172A",
    "supportEmail": "[email protected]"
  }
}

If you omit programId and your tenant has exactly one program, the platform resolves it. With more than one program, programId is required.

List your programs with GET /wallet/programs/me. That endpoint is read-only; programs are created and edited from the merchant panel.

End user

An end user is the wallet holder. There are two types:

accountTypeWho
PERSONALYour individual customer — tops up, sends, pays
BUSINESSA business inside the program — collects payments, subject to commission

End users are deduplicated by phone number within a program. The same phone never creates a second user in the same program.

{
  "programId": "5b9e2f3a-7c41-4d8e-a6f2-9e1b3c4d5f60",
  "accountType": "PERSONAL",
  "phone": "+905321234567",
  "externalCustomerId": "crm-88213",
  "displayName": "A. Yilmaz"
}

externalCustomerId is the identifier in your own system; GET /wallet/end-users/by-external/:extId lets you reach the user by your id, so you never have to store ours.

⚠️

The phone number is the deduplication key. Binding a wallet to an unverified phone means handing the wallet to whoever takes over that number. The portal flow enforces OTP verification; on the s2s surface, verification is your responsibility.

Wallet and account

These are two layers, and conflating them is the most common integration mistake:

  • Wallet — the user's container inside a program. It has a name and there can be several ("Spending", "Savings").
  • Wallet account — the balance of a wallet in one currency. Money always sits in an account.
End user, wallet and accountTree: an end user holds a wallet, and the wallet holds one account per currency. The balance lives on the account, not on the wallet.1 : NBALANCE HEREEnd userWallet "Spending"Account · TRYAccount · USDLEGENDIDENTITYHOLDS BALANCE
A separate account is opened per currency and the balance lives there. The wallet is a container; the account is what actually carries the ledger balance — your amount queries work with the account id.

All money-movement endpoints take an account id (sourceWalletAccountId, creditWalletAccountId), never a wallet id.

There is no balance column; the balance is a projection of the ledger. GET /wallet/end-users/:id/balances returns the current ledger-derived balance.

Operation

Every money-moving call produces an operation — a first-class object with a record, a status, fees and audit history. Operation types:

TypeWhat
TOPUP_CARDCard top-up
TOPUP_TRANSFERBank transfer top-up
TOPUP_AGENTCash top-up via an agent
TOPUP_REFUNDRefund of a card top-up
W2W_TRANSFERWallet-to-wallet transfer
W2IBAN_WITHDRAWTransfer to a third party's IBAN
WITHDRAW_BANKWithdrawal to the user's own IBAN
PAYMENT_BUYWallet payment (individual → business)

There are also cash-out, card authorization, crypto and refund types. For the status machine see Overview; for listing and receipts see Operations.

⚠️

W2IBAN_WITHDRAW and WITHDRAW_BANK share the same bank integration but are distinct types: one is a transfer to a third party, the other a withdrawal to the user's own account. Reporting, limit buckets and fee schedules count them separately.

Suspense funds

If money arrives on your account but cannot be attributed to a user, it is not lost — it goes to suspense. From the panel it is either matched (bound to a deposit instruction) or returned to the sender. This is the safety net that stops "money arrived, but whose?" from being silently credited.

Idempotency

Idempotency-Key is mandatory on money-moving calls and on calls that create records (end users, wallets, accounts, QR codes, bulk payouts …); the full list is on the Authentication page.

-H "Idempotency-Key: order-9988-topup-1"

A second request with the same key does not repeat the operation; it returns the first request's result. Derive the key from your business meaning (order number + attempt) rather than generating a fresh UUID on each retry — otherwise a retry after a network error becomes a second money movement.

quote → execute → complete

Most outbound operations take three steps:

StepWhat happensMoney moves?
POST /…/quoteReturns fee, tax, limit eligibilityNo
POST /…Opens the operation, reserves funds, sends OTP if neededReserved
POST /…/:id/completeConfirms with integrityHash + nonce (+ otpCode)Yes

integrityHash proves the amount quoted is the amount settled. If what you showed the user differs from what would leave the account, the request is rejected — which makes hiding a fee from the user structurally impossible.

nonce is single-use. If a complete call fails, do not retry with the same nonce — query the operation and decide based on its status.

Next