Appearance
Wallet debits
Debit a wallet with the customer freshly authenticated
POST
/api/v1/tenant/wallet-debitsScope wallet-debits:write · Write — send Idempotency-Key and X-Request-Id
Moves the amount from the customer's wallet to the mandate's beneficiary immediately; no payment provider is involved. The assertion is validated on every call and must be bound to this request, with mandateUid as the binding target (see the Customer authentication guide); the mandate must be active, within its validity window and satisfy its amount rule, but its frequency is not enforced here — that applies to POST /tenant/mandates/{uid}/debits. The debit is all-or-nothing: available funds short of the amount are a 422 and nothing is debited. The response state is terminal immediately and wallet_debit.completed is delivered to your webhook.
Request body
| Field | Type | Required | Description |
|---|---|---|---|
walletUid | string | yes | The wallet to debit, as returned by POST /tenant/customers. |
mandateUid | string | yes | An active mandate of the wallet's customer that you hold (POST /tenant/mandates); its beneficiary receives this debit. |
amountMinor | number | yes | Positive integer minor units (e.g. 250000 = ETB 2,500.00); must satisfy the mandate's amount rule. Min 1. |
currency | string | yes | ISO-4217; must equal the wallet's currency. |
clientReference | string | yes | Your own reference; stored and returned verbatim, never interpreted; unique within your account. |
assertion | object | yes | The customer's authentication for this debit. |
assertion.customerUid | string | yes | The customer who authenticated. |
assertion.identifierUid | object | no | The customer identifier the customer authenticated with, if known. |
assertion.assuranceLevel | enum | yes | How strongly your channel authenticated the customer. One of single_factor, two_factor, step_up. |
assertion.factor1Type | string | yes | The first factor used, e.g. pin, password, biometric. At most 32 characters. |
assertion.factor2Type | object | no | Required unless assuranceLevel is single_factor. At most 32 characters. |
assertion.authenticatedAt | string | yes | ISO-8601 — when the customer authenticated. |
assertion.expiresAt | string | yes | ISO-8601; by default at most 10 minutes after authenticatedAt. An expired assertion is refused (422). |
assertion.channelCode | string | yes | Your channel the customer authenticated on. At most 40 characters. |
assertion.deviceHash | object | no | SHA-256 hex of the device fingerprint, if your channel has one. |
assertion.sourceReference | string | yes | Opaque channel evidence reference (a session or login id). Presenting one again while it is live is allowed only for the same customer, and is audited. At most 160 characters. |
assertion.transactionBinding | object | no | Hash your channel computed over the request this assertion authorizes; a mismatch with the request received is refused. |
assertion.transactionBinding.hash | string | yes | SHA-256 hex (64 characters) over the canonical string of this request. |
assertion.transactionBinding.algorithm | enum | yes | One of sha256. |
assertion.transactionBinding.canonical | enum | yes | The field template hashed: the six values joined with |, UTF-8. The fifth is the payout destination, the mandate for a debit, or empty; clientReference is empty when absent. One of `customerUid |
Example (generated from the schema)
json
{
"walletUid": "01J9Z3K5X8QW2R4T6V8Y0B1C3D",
"mandateUid": "01J9Z3K5X8QW2R4T6V8Y0B1C3D",
"amountMinor": 1,
"currency": "ETB",
"clientReference": "string",
"assertion": {
"customerUid": "01J9Z3K5X8QW2R4T6V8Y0B1C3D",
"assuranceLevel": "single_factor",
"factor1Type": "string",
"authenticatedAt": "2026-09-03T10:15:30.000Z",
"expiresAt": "2026-09-03T10:15:30.000Z",
"channelCode": "string",
"sourceReference": "string"
}
}Response · 201
| Field | Type | Description |
|---|---|---|
uid | string | |
type | string | Operation type, e.g. wallet_credit, wallet_debit, payout_b2c or topup_c2b. |
state | string | Operation state: created, awaiting_authorization, authorized, held, processing, pending_provider, confirmed, failed, reversed or completed. |
amountMinor | number | Integer minor units (e.g. 250000 = ETB 2,500.00). |
feeMinor | number | Minor units. |
taxMinor | number | Minor units. |
currency | string | ISO-4217. |
clientReference | string | null | Your reference, exactly as you supplied it. |
createdAt | string | |
completedAt | string | null | When the operation first reached a final state. |
links | object | |
links.self | string | API-relative path of this operation (GET /tenant/operations/{uid}). |
links.receipt | string | API-relative path of its receipt (GET /tenant/operations/{uid}/receipt). |
Example (generated from the schema)
json
{
"uid": "01J9Z3K5X8QW2R4T6V8Y0B1C3D",
"type": "string",
"state": "string",
"amountMinor": 0,
"feeMinor": 0,
"taxMinor": 0,
"currency": "ETB",
"clientReference": "string",
"createdAt": "2026-09-03T10:15:30.000Z",
"completedAt": "2026-09-03T10:15:30.000Z",
"links": {
"self": "string",
"receipt": "string"
}
}Errors
400 401 403 404 409 422 429 500 503 — one body shape for all of them, see Errors.