Quickstart
- Base path
/api/v1/wallet- Identity
- Ed25519 request signature —
X-Key-Id,X-Date,X-Nonce,Digest(when there is a body),X-Signature. ±5 min clock window, single-use nonce, optional per-key IP allow-list. - Authorization
- The key must carry the wallet product and the wallet permission the call requires; a call without it gets
403 WALLET_INSUFFICIENT_SCOPE. Permissions per endpoint: Key permissions. - Cross-surface
- These endpoints are the s2s surface — call them from your own server with a signed request.
This guide takes you from zero to a working wallet flow: create a user → top up → transfer → confirm. Every step uses a path that runs in production today; nothing here depends on a provider that is not yet connected.
All amounts are minor-unit strings: "amount": "10050" means ₺100.50. Every request is Ed25519-signed — signature headers are abbreviated as … below. Send request bodies as compact JSON (no line breaks or extra spaces) and compute Digest over exactly that string; the bodies below are spread over several lines only for readability.
Verify signing
Before moving money, prove your signing works with the simplest endpoint:
curl "https://api-gateway.lunixi.com/api/v1/wallet/ping" \
-H "X-Key-Id: $LUNIXI_KEY_ID" \
-H "X-Date: $DATE" -H "X-Nonce: $NONCE" -H "X-Signature: $SIG"The key needs a set-up permission (wallet:program:manage or wallet:enduser:manage) for this call. Do not continue until you get a 200. If not, see Authentication → Error codes.
Find your program
curl "https://api-gateway.lunixi.com/api/v1/wallet/programs/me" \
-H "X-Key-Id: …" -H "X-Date: …" -H "X-Nonce: …" -H "X-Signature: …"Note your program's id: you send it as programId when creating end users, and in other calls that take a program.
Create an end user
Users are unique by phone within a program — a second call with the same phone and a new key does not create another user; it returns 409 WALLET_ENDUSER_EXISTS, while a repeat with the same Idempotency-Key returns the first response. All fields and rules: Create an End User.
curl -X POST "https://api-gateway.lunixi.com/api/v1/wallet/end-users" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: signup-crm-88213" \
-H "X-Key-Id: …" -H "X-Date: …" -H "X-Nonce: …" -H "Digest: …" -H "X-Signature: …" \
-d '{
"programId": "5b9e2f3a-7c41-4d8e-a6f2-9e1b3c4d5f60",
"accountType": "PERSONAL",
"phone": "+905321234567",
"externalCustomerId": "crm-88213",
"displayName": "A. Yilmaz"
}'The response returns the user together with the default wallet and its accounts:
{
"status": "success",
"code": "SUCCESS",
"data": {
"endUser": { "id": "8d4c2a1f-3b6e-4f7a-9c5d-2e8b1a0f6c34", "accountType": "PERSONAL", "status": "ACTIVE" },
"defaultWallet": {
"id": "1e7f9a3b-5c2d-4a8e-b6f1-3d9c0e2a7b58",
"walletNo": "5810763412",
"name": "Cüzdanım",
"isDefault": true,
"accounts": [
{ "id": "c2a5e8f1-9d3b-4c7a-8e6f-5b1d2a9c0e47", "currency": "TRY", "fullWalletNo": "58107634125000", "status": "ACTIVE" }
]
}
}
}externalCustomerId is your identifier. If you would rather not store ours, reach the user with GET /wallet/end-users/by-external/crm-88213?programId=….
Open an extra account if needed
The default wallet opens with a single account, in your program's main currency (or the currency you sent as defaultCurrency). For another currency:
curl -X POST "https://api-gateway.lunixi.com/api/v1/wallet/wallets/1e7f9a3b-5c2d-4a8e-b6f1-3d9c0e2a7b58/accounts" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: acct-crm-88213-usd" \
-H "X-Key-Id: …" -H "X-Signature: …" \
-d '{ "currency": "USD" }'Top up by card
Card top-ups are delegated to the payment stack: the response gives you either hosted checkout content or a 3D Secure redirect.
curl -X POST "https://api-gateway.lunixi.com/api/v1/wallet/topups/card" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: topup-crm-88213-1" \
-H "X-Key-Id: …" -H "X-Signature: …" \
-d '{
"creditWalletAccountId": "c2a5e8f1-9d3b-4c7a-8e6f-5b1d2a9c0e47",
"amount": "25000",
"currency": "TRY",
"returnUrl": "https://app.acme.com/wallet/topup/result"
}'{
"status": "success",
"code": "SUCCESS",
"data": {
"operationId": "4f1b8d2e-6a9c-4b3e-8f7d-1c5e9a2b6d08",
"paymentId": "9c6f2b1a-4e8d-4a3f-b5e6-2d7c1a9b8e30",
"status": "INITIALIZED",
"checkoutFormContent": "<script …>",
"redirectUrl": ""
}
}Mount checkoutFormContent in the user's browser; card data never touches your server. When the user completes payment the operation becomes COMPLETED and wallet.topup.completed fires.
Do not learn the top-up outcome from the user returning to returnUrl — the browser may never come back. The webhook is the source of truth; confirm with GET /wallet/topups/{operationId}.
Check the balance
curl "https://api-gateway.lunixi.com/api/v1/wallet/end-users/8d4c2a1f-3b6e-4f7a-9c5d-2e8b1a0f6c34/balances" \
-H "X-Key-Id: …" -H "X-Signature: …"{
"status": "success",
"code": "SUCCESS",
"data": {
"balances": [
{
"walletAccountId": "c2a5e8f1-9d3b-4c7a-8e6f-5b1d2a9c0e47",
"currency": "TRY",
"balance": "25000",
"fullWalletNo": "58107634125000"
}
]
}
}Quote the transfer first
For outbound money you must call quote first — the user has to see the fee before the debit.
curl -X POST "https://api-gateway.lunixi.com/api/v1/wallet/transfers/w2w/quote" \
-H "Content-Type: application/json" \
-H "X-Key-Id: …" -H "X-Signature: …" \
-d '{
"endUserId": "8d4c2a1f-3b6e-4f7a-9c5d-2e8b1a0f6c34",
"sourceWalletAccountId": "c2a5e8f1-9d3b-4c7a-8e6f-5b1d2a9c0e47",
"destinationPhone": "+905339876543",
"amount": "10050",
"currency": "TRY"
}'{
"status": "success",
"code": "SUCCESS",
"data": {
"feeTotal": "250",
"taxTotal": "125",
"totalCharge": "10425",
"requiredAmount": "10425",
"maskedDestinationName": "Meh*** O***",
"sourceBalance": "25000",
"sufficientBalance": true,
"limitBlocked": false,
"scheduleVersionId": "2d8b5e1c-7f4a-4c9e-a3b6-8e1f5c2d9a74"
}
}If sufficientBalance is false or limitBlocked is true, do not start the transfer — show the user why. maskedDestinationName exists so the sender can confirm the recipient is the right person.
Initialize the transfer
curl -X POST "https://api-gateway.lunixi.com/api/v1/wallet/transfers/w2w" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: p2p-crm-88213-1" \
-H "X-Key-Id: …" -H "X-Signature: …" \
-d '{
"endUserId": "8d4c2a1f-3b6e-4f7a-9c5d-2e8b1a0f6c34",
"sourceWalletAccountId": "c2a5e8f1-9d3b-4c7a-8e6f-5b1d2a9c0e47",
"destinationPhone": "+905339876543",
"amount": "10050",
"currency": "TRY",
"scheduleVersionId": "2d8b5e1c-7f4a-4c9e-a3b6-8e1f5c2d9a74",
"otpDestination": "+905321234567",
"otpChannel": "SMS"
}'{
"status": "success",
"code": "SUCCESS",
"data": {
"operationId": "7a3e9c1d-2b4f-4e8a-9d6c-0f5b8e2a1c93",
"status": "CHALLENGED",
"otpRequired": true,
"otpChannel": "SMS",
"integrityHash": "9c1f…",
"nonce": "4b82…"
}
}Funds are reserved; nothing has moved yet. When otpRequired is true, collect the code from the user.
Complete the transfer
curl -X POST "https://api-gateway.lunixi.com/api/v1/wallet/transfers/w2w/7a3e9c1d-2b4f-4e8a-9d6c-0f5b8e2a1c93/complete" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: p2p-crm-88213-1-complete" \
-H "X-Key-Id: …" -H "X-Signature: …" \
-d '{
"endUserId": "8d4c2a1f-3b6e-4f7a-9c5d-2e8b1a0f6c34",
"operationId": "7a3e9c1d-2b4f-4e8a-9d6c-0f5b8e2a1c93",
"integrityHash": "9c1f…",
"nonce": "4b82…",
"otpCode": "482913"
}'{
"status": "success",
"code": "SUCCESS",
"data": {
"operationId": "7a3e9c1d-2b4f-4e8a-9d6c-0f5b8e2a1c93",
"status": "COMPLETED",
"ledgerTxnRef": "ltx_01J8ZU…",
"receiptUrl": "https://api-gateway.lunixi.com/api/v1/wallet/operations/7a3e9c1d-2b4f-4e8a-9d6c-0f5b8e2a1c93/receipt"
}
}integrityHash and nonce must be carried over verbatim. The nonce is single-use; if complete errors, query the operation instead of retrying with the same nonce.
Wire up webhooks
Learn async outcomes from webhooks, not polling. Register your endpoint in the panel and subscribe to:
| Event | When |
|---|---|
wallet.topup.completed | A card/bank top-up landed in the balance |
wallet.topup.failed | The top-up failed |
wallet.transfer.completed | The transfer completed |
wallet.transfer.failed | The transfer failed and the reservation was released |
Envelope and signature verification: Webhooks.
Next steps
- Core Concepts — program, account, operation vocabulary
- Limits and Fees — to understand rejections
- Portal & SDK — if you are building the end-user app
- Roadmap — status of bank integrations, bill pay, ATM and more