Wallet
Bulk Payouts

Bulk Payouts

Who calls these endpoints?External API (s2s)
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
Nine bulk-payout endpoints exist on s2s and are SHARED with the panel. 5 panel endpoints have no s2s equivalent: POST bulk-payouts/:id/cancel, GET bulk-payouts/:id/preview, GET bulk-payouts/:id/statement, GET bulk-payouts/counts, GET bulk-payout-statements/period.

Bulk payouts let you pay many recipients from a single list — payroll, a refund batch, campaign rewards, dealer commissions. The batch is approved as a whole, items are processed one by one, and each item carries its own status.

Coming soon — non-wallet targets require a bank integration

Batch creation, the approval chain and item tracking are built. Batches whose targets are inside the wallet work in the closed loop; items targeting an IBAN require a connected bank integration. While no integration is connected, IBAN-targeted items are not accepted. Status: Roadmap.

Flow

Bulk payout batch lifecycleState machine: a batch opens as a draft, goes for approval, is processed once approved and completes. If some items fail, the batch ends partially failed.SUBMITAPPROVEALL OKREJECTSOME FAILEDDRAFTPENDING_APPROVALPROCESSINGCOMPLETEDREJECTEDPARTIALLY_FAILEDLEGENDWAITINGNEEDS ACTIONEND
PARTIALLY_FAILED is an item failure, not a batch one: the items that passed were paid. Do not recreate the batch — use retry-failed, or the successful items get paid twice.

Create the batch

POST /api/v1/wallet/bulk-payouts

Send an Idempotency-Key header with this call; the submit, approve, reject, process and retry-failed calls do not take one.

FieldTypeRequiredDescription
programIdstring✅
sourceWalletAccountIdstring✅Account the payouts leave from
currencystring✅Batch-wide currency
itemsarray✅Line items
reasonstring—Audit reason

Each item:

FieldTypeRequiredDescription
targetTypestring✅Target kind (wallet / IBAN)
targetRefstring✅Wallet number, phone or IBAN
beneficiaryNamestring—Expected for IBAN targets
amountstring✅Minor-unit
{
  "programId": "5b9e2f3a-7c41-4d8e-a6f2-9e1b3c4d5f60",
  "sourceWalletAccountId": "c2a5e8f1-9d3b-4c7a-8e6f-5b1d2a9c0e47",
  "currency": "TRY",
  "reason": "July dealer commissions",
  "items": [
    { "targetType": "WALLET", "targetRef": "90455132", "amount": "125000" },
    { "targetType": "WALLET", "targetRef": "+905339876543", "amount": "87500" }
  ]
}

Submit for approval

POST /api/v1/wallet/bulk-payouts/{id}/submit

Approve or reject

POST /api/v1/wallet/bulk-payouts/{id}/approve   # { "approve": true, "reason": "…" }
POST /api/v1/wallet/bulk-payouts/{id}/reject    # { "reason": "…" }
⚠️

Maker-checker is mandatory: the person who created the batch cannot approve it. This is enforced by principal comparison, not a role gate — even though OWNER/ADMIN hold both permissions, the same user cannot approve their own batch.

On the s2s surface the approver is the signing key: approve with a different key from the one that created the batch. The maker key needs wallet:bulk:create and wallet:bulk:approve (to create and submit); the checker key needs wallet:bulk:approve. See Key permissions.

Process

POST /api/v1/wallet/bulk-payouts/{id}/process

Items are processed in order. While running, the batch is PROCESSING.

Retry failures

POST /api/v1/wallet/bulk-payouts/{id}/retry-failed

Only failed items are retried; successful items are never paid twice.

Tracking

GET /api/v1/wallet/bulk-payouts                  # batches (filter by programId, status)
GET /api/v1/wallet/bulk-payouts/{id}             # batch summary
GET /api/v1/wallet/bulk-payouts/{id}/items       # items (filter by status)

Item status is independent of batch status: a COMPLETED batch may still contain failed items. Record payments in your own system from item statuses, not the batch status.

⚠️

If the balance runs short, the batch is processed partially. Never recreate a PARTIALLY_FAILED batch from scratch — use retry-failed, otherwise the successful items get paid a second time.

Statements

For the accounting statement of payout batches:

GET /api/panel/v1/wallet/bulk-payouts/:id/statement      # single batch
GET /api/panel/v1/wallet/bulk-payout-statements/period   # period

The period statement is filtered by source wallet account (sourceWalletAccountId) and date range (fromDate, toDate — defaults to the last 30 days).

⚠️

The period statement path is bulk-payout-statements/period — the trailing /period segment is mandatory. Dropping it and calling bulk-payout-statements hits an endpoint that does not exist.

Panel side: Panel — Bulk Payouts.

Common mistakes

SymptomCauseFix
Approval rejectedApprover is the same person as the creatorA different operator must approve
Batch PARTIALLY_FAILEDSome targets invalid or balance insufficientInspect the item list, run retry-failed
The same payout went out twiceThe batch was recreatedUse retry-failed, not a new batch
IBAN-targeted items rejectedNo bank integration connectedSee the roadmap box above