Mass payouts overview

Pay many recipients from a single funded deposit.

Mass payouts let you pay many recipients from a single funded deposit. Upload a CSV, payzum splits it into batches, broadcasts each batch as a separate on-chain transaction, and webhooks you throughout.

How it works

  1. Upload a CSV — POST the file to the create endpoint for your chain family (UTXO or EVM). payzum parses every row, validates each address, and computes a fee quote.
  2. Review and confirm the quote — The response includes the full quote breakdown. Call confirm when you are ready to fund. This advances the order to pending_deposit.
  3. Fund the deposit address — Send exactly totalToSendRaw base units to the depositAddress returned in the order. payzum detects the deposit on-chain.
  4. Batches broadcast automatically — Once the deposit is confirmed payzum splits recipients into batches and broadcasts each as a separate transaction. You receive a webhook for each batch event.
  5. Terminal state and settlement report — When all batches settle the order transitions to completed (or partial_failed if some batches failed). A settlement CSV is generated and linked in the mass_payout.completed webhook. UTXO orders also expose it at GET /v1/mass-payout/:id/report.csv; EVM orders deliver it only through the webhook's reportR2DownloadUrl.

Authentication, errors, and conventions

All mass-payout endpoints require the x-api-key header with your merchant API key.

Errors are returned in a { "ok": false, "error": { "kind": "..." } } envelope where kind is a snake_case error code — see the per-family error catalogs. Two exceptions: failed authentication and rate limiting are handled by the shared gate also used by /v1/payment, so they return the canonical { "statusCode", "code", "message" } envelope instead. The rate limit is shared with the payments API: 60 requests per minute per merchant.

Unlike the payments API (snake_case), the mass-payout APIs use camelCase field names (sumRecipientsRaw, depositAddress, batchIndex, …). This is intentional — the naming convention differs per API surface.

Pagination

List endpoints (GET /v1/mass-payout, GET /v1/evm-mass-payout) accept:

  • page — default 1, 1-based. Note this differs from the payments API, which paginates from page 0.
  • limit — default 50, max 200.
  • status, chain — optional filters; invalid values are silently ignored.

The response is { "ok": true, "orders": [...], "page", "limit" } — no total count is returned. In list responses each order's batches array is always empty; only the fetch-by-id endpoint populates batch detail.

Recipient endpoints (GET .../:id/recipients) accept page and limit (default 100, max 1000) plus a batchIndex filter. Each item contains rowIndex, address, amountRaw (string, raw base units), amountDecimal (string, decimal), and label — EVM items additionally include status, txHash, and batchIndex.

CSV format

The same CSV format applies to both UTXO and EVM mass payouts. A header row is required.

| Column | Required | Description | |--------|----------|-------------| | address | yes | Chain-native recipient address | | amount | yes | Decimal string in whole coins or tokens (e.g. 0.001 for BTC, 10.00 for USDC) | | label | no | Optional label for audit trail |

Example CSV:

address,amount,label
bc1qxy2kgdygjrsqtzq2n0yrf2493p83kkfjhx0wlh,0.001,invoice-1234
bc1q9h6yzs4h88e5tdfud6flrugv0tphq0fgfwytdr,0.0005,invoice-5678

All addresses in a single CSV must belong to the same chain. payzum validates each address against the chain specified in the request and rejects rows with mismatched formats.

Quote fields

The quote object appears in the order response immediately after creation. All *Raw fields are integer strings in the chain's smallest unit (satoshi / koinu / duff for UTXO; token base units for EVM).

| Field | Description | |-------|-------------| | sumRecipientsRaw | Sum of all output amounts in base units | | networkFeeRaw | Estimated miner or gas fee across all batches | | payzumFeeRaw | Service fee (payzumFeeBps basis points of sumRecipientsRaw) | | totalToSendRaw | Total deposit required: sumRecipientsRaw + networkFeeRaw + payzumFeeRaw | | payzumFeeBps | Fee rate in basis points (100 = 1%) |

Fund the deposit address with exactly totalToSendRaw. Sending less causes the order to transition to underfunded after the expiry window; sending more is accepted and the overage is handled per chain policy.

Idempotency

The create endpoint accepts an optional Idempotency-Key request header. Pass a UUID or other unique token per logical operation. Reusing a (merchant, Idempotency-Key) pair returns 409 with kind: idempotency_conflict on both UTXO and EVM — the original order is not replayed in the response; fetch it with the GET-by-id endpoint instead. Keys have no TTL: a used key does not expire.

Webhooks

payzum emits signed webhook events throughout the order lifecycle. Webhook payloads use the same camelCase convention as the rest of this API (eventType, eventId, eventAt). See Mass-payout webhooks for the full event catalog and signature verification.

Key events:

  • mass_payout.created — order confirmed, ready for funding
  • mass_payout.deposit_detected — deposit seen on-chain
  • mass_payout.batch_broadcasted / mass_payout.batch_confirmed — per-batch progress
  • mass_payout.completed — all batches settled; settlement CSV linked
  • mass_payout.partial_failed — some batches failed; operator action may be needed

Chain families

  • UTXO (BTC, LTC, DOGE) — HD-derived deposit address whose signing key Payzum uses during execution, so the batch is in Payzum's transitory custody while it runs. See UTXO mass payouts.
  • EVM (Polygon and more) — on-chain escrow contract; merchant deposits into the contract and Payzum's operator wallet pays gas to distribute. See EVM mass payouts.

Note that this custody model applies to payouts only: the payments (collection) flow remains non-custodial.