WalletD developers
Guides

Payouts: money out

Money out of the loop: the ledger moves first, and the payout record is the instruction to make the real transfer.

A merchant earns into a settlement account. A payout moves that balance out of the loop and produces the instruction to pay it.

This is a semi-closed wallet: consumers cannot cash out to a bank. Money leaves through merchant payouts and nowhere else. That is a product decision with regulatory consequences, not a missing feature.

Books first, transfer second

The ledger moves first. The payout record is the instruction to make a real transfer, and it carries external_ref for the reference your bank rail gives back.

That ordering is deliberate. Books that lead reality can be reconciled; books that trail it cannot answer "did we already pay this?" during an incident.

Run one

curl -sS -X POST "$WALLETD_API/v1/clients/$CLIENT_ID/payouts" \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: payout-2026-08-11-$CLIENT_ID" \
  -d '{}'
{
  "id": "019fee0d-be2a-7090-97ef-c1f7c3162ef6",
  "client_id": "019f...",
  "amount": 2000,
  "rail": "manual",
  "status": "completed",
  "external_ref": null,
  "created_at": "2026-08-11T05:41:52.006Z"
}

An empty body pays out the full settlement balance. The settlement account is zero afterwards, and a second run against a zero balance is refused with 422 nothing_to_pay rather than creating an empty payout.

Derive the idempotency key from the payout period, not the moment: payout-2026-08-11-{client} retried after a timeout pays once, where a timestamped key would pay twice.

func (c *Client) RunPayout(ctx context.Context, clientID, period string) (*Payout, error) {
	var payout Payout
	// The key is the period, so a retry after a timeout cannot pay twice.
	key := "payout-" + period + "-" + clientID
	err := c.Do(ctx, http.MethodPost, "/v1/clients/"+clientID+"/payouts", map[string]any{}, &payout, key)
	return &payout, err
}

List and reconcile

curl -sS "$WALLETD_API/v1/clients/$CLIENT_ID/payouts" -H "Authorization: Bearer $TOKEN"
curl -sS "$WALLETD_API/v1/merchants/$MERCHANT_ID/statement" -H "Authorization: Bearer $WALLETD_API_KEY"

The statement is the merchant-facing document: what they earned, what was deducted, what was paid out, and when. It is what a merchant's finance team reconciles against their own books.

Rails

rail is manual today: the payout is a ledger movement plus an instruction a human or a treasury job executes. external_ref is where you record the bank reference once the transfer is made, which closes the loop for reconciliation.

The field is free-form on purpose. When a local payout rail is added, existing payouts keep their shape and your integration does not change: the same call, a different rail value, and external_ref filled in automatically instead of by you.

The float, before and after

A payout shrinks both sides of the float equation at once: settlement goes down, treasury goes up, and money that left the loop is accounted for in treasury rather than vanishing.

curl -sS "$WALLETD_API/v1/ledger/summary" -H "Authorization: Bearer $WALLETD_API_KEY"

If you reconcile against your bank, this is the endpoint that tells you what should have left. See The money model.

What can go wrong

CodeStatusWhat happened
nothing_to_pay422The settlement balance is zero
not_client_member403The caller is not a member of that client's organization
client_not_found404Bad client id, or not visible to you

Next

On this page