---
title: "Mass payouts API architecture: how to pay thousands of recipients across borders (2026)"
seoTitle: "Mass payouts API architecture for platforms (2026)"
description: "A reference architecture for mass payouts: queue, quote, execute, webhooks and reconciliation, with a failure table, monitoring checklist and partner fees."
date: "2026-10-29"
updated: "2026-10-29"
category: "payments"
author: "BlindPay Team"
faq:
  - q: "How many payouts can I send at once?"
    a: "BlindPay docs do not describe a bulk or batch endpoint, so each recipient is one quote and one payout, and you control concurrency in your own queue. Development instances cap requests at 40 per minute per instance, while production instances have no per-instance request cap. Size your workers against your own infrastructure and test the pace on a development instance first."
  - q: "How do I monetize payouts as a platform?"
    a: "Create a partner fee configuration, a percentage in basis points, a flat amount in cents, or both, and pass its partner_fee_id on each payout quote. The exact amount collected appears as partner_fee_amount on the quote. Fees accumulate over the calendar month and are released for withdrawal on the first day of the next month, net of your BlindPay invoice."
  - q: "What if a bank returns or rejects a payout?"
    a: "The documented terminal states are completed, failed and refunded, so a rejected payout surfaces as failed or refunded rather than a separate returned state. A refund sends the stablecoins back to the refund wallet on the quote, or to the sending wallet. A failed payout is not refunded automatically, so contact support@blindpay.com."
  - q: "How do I reconcile thousands of payouts?"
    a: "Treat webhooks as the source of events and your ledger as the source of truth. Store the quote id, payout id and your own payment id for every line, consume payout.new, payout.update and payout.complete, dedupe by the svix-id header, and close a run only when every payout id has a terminal status. Then compare totals against your records."
  - q: "Do recipients need to be verified before the first payout?"
    a: "Yes. Every payment flows through a customer that has completed KYC, and each payout needs an approved bank account. The platform collects the data and BlindPay verifies it. KYC Standard takes about 60 seconds when automated, while KYC Enhanced and KYB Standard take 3 hours to 1 business day with manual review."
  - q: "Can I test failure handling before going live?"
    a: "Yes. Development instances are free and complete payouts automatically. Setting the request_amount to 66600 forces a failed payout and 77700 forces a refunded one. Use them to prove that your queue, webhook handler and ledger handle both outcomes before real money moves."
---

A mass payouts architecture is a queue in front of a quote-then-execute pair per recipient, with webhooks feeding a ledger and a reconciliation job closing the run. BlindPay docs describe no bulk endpoint, so scale comes from your workers. Payout states are `processing`, `on_hold`, `completed`, `failed` and `refunded`.

This guide is the reference architecture for marketplaces, creator platforms and other senders of thousands of payouts, as of October 2026. For the basics, start with [what a stablecoin payouts API is](/resources/more/what-is-a-stablecoin-payouts-api). Use-case pages cover [marketplaces in LATAM](/resources/more/marketplace-stablecoin-payouts-latam), [creator platforms](/resources/more/creator-platform-payouts-stablecoins) and [affiliate publishers](/resources/more/affiliate-publisher-payouts-stablecoins). This page is about the system design.

**Key takeaways**

- Every payout is one quote and one execute call. A `quote_id` can back only one payout, and a quote locks rate and fees for 5 minutes by default.
- BlindPay documents five payout states: `processing`, `on_hold`, `completed`, `failed` and `refunded`. Build your state machine on those and nothing else.
- A `failed` payout is not refunded automatically. Contact support@blindpay.com instead of assuming it resolves.
- Platforms earn on payouts through partner fees. The amount collected is `partner_fee_amount` on the quote.
- Production instances have no per-instance request cap, while development instances are capped at 40 requests per minute per instance.

## What does a mass payouts architecture look like?

It has six components, each with one job. Draw them as a line from left to right.

1. **Payout intake.** Your marketplace or app writes a payout request: recipient, amount, currency side, your own payment id. This is your ledger's source of truth.
2. **Queue.** Requests wait here. Workers pull one, so you control the pace, retries and priority.
3. **Quote worker.** Creates a payout quote for the request and reads `expires_at`.
4. **Execute worker.** Executes the quote immediately, with an `Idempotency-Key`, and stores the payout id.
5. **Webhook handler.** Receives `payout.new`, `payout.update` and `payout.complete`, verifies the signature, dedupes, and updates the ledger.
6. **Reconciler.** Compares your ledger with BlindPay's records, flags missing terminal states and drives retries.

The three BlindPay steps behind this are on the [payouts page](/docs/payouts): add a bank account once per recipient, create a quote, execute. The [step-by-step guide](/resources/more/how-stablecoin-payouts-work-step-by-step) explains them. For the build checklist, see the [integration checklist](/resources/more/stablecoin-payouts-api-integration-checklist).

## How do you queue thousands of payouts?

Queue by rail and let each worker do a quote and an execute back to back. Do not quote the whole list first, because quotes expire.

A quote locks rate and fees for 5 minutes by default, and `expires_at` comes back in epoch milliseconds. SEPA and Faster Payments quotes can be shorter. A worker that holds a quote for seconds is fine. A worker that pulls 500 quotes and executes them over twenty minutes will execute stale ones and get rejections.

Practical rules:

- One worker action is quote then execute. Never store a quote for later.
- If execute is rejected because the quote expired, create a new quote. Never reuse the old `quote_id`.
- Fund the source wallet before the run with a buffer, since rates move across a long run. [How much USDC to hold for payouts](/resources/more/how-much-stablecoin-float-for-payouts) covers sizing.
- Use an `Idempotency-Key` on each execute call. Keys are kept for 24 hours and a retry with the same bytes replays the stored response, per the [idempotency docs](/docs/learn/idempotency).

There is no documented bulk endpoint. A batch is a loop with your queue in charge. [Why one quote cannot cover a batch](/resources/more/batch-stablecoin-payouts-live-quotes) goes deeper on the loop.

## Which states can a payout move through?

Five, and only five are documented. Model exactly these.

| State | Meaning | Terminal? | Your action |
| --- | --- | --- | --- |
| `processing` | Default on creation; funds being pulled and fiat transfer in flight | No | Wait for webhooks |
| `on_hold` | Held for compliance review; all SWIFT payouts start here, and ACH, wire and RTP pass through it | No | Wait; holds can take up to 30 days and a timeout fails the payout |
| `completed` | Fiat landed in the recipient's bank account | Yes | Mark paid |
| `failed` | Payout did not complete, for example a review timeout or rejected compliance check | Yes | Contact support, fix the cause, requote |
| `refunded` | Stablecoins returned to the funding source instead of converted | Yes | Fix the account and requeue |

Sub-status objects, `tracking_transaction`, `tracking_payment`, `tracking_liquidity` and `tracking_complete`, carry a `step` with detail beyond the top-level status, such as `pending_review` or `pending_refund_review`. Store them for support cases, but drive your ledger from the top-level `status`. [Payout statuses explained](/resources/more/stablecoin-payout-statuses-explained) defines each one.

The webhook events are `payout.new`, `payout.update` and `payout.complete`. Per the [webhook events reference](/docs/learn/webhooks-events), `payout.update` fires when a payout moves through an intermediate step or reaches `failed`, and `payout.complete` fires when it finishes or is refunded.

## How do you handle failures at scale?

Map every failure to one automated response and one human escalation. The table covers five cases.

| Failure | What you see | Automated response | Escalate when |
| --- | --- | --- | --- |
| Quote expired | Execute rejected after `expires_at` | Create a new quote, execute it | Repeats for the same row |
| Below minimum | `LIMITS_AMOUNT_OUT_OF_RANGE`, or a SWIFT or SEPA minimum error at quote time | Mark the row invalid, do not retry | Always, to the product owner |
| Bank rejected | Payout ends `failed` | Park the row, open a support ticket | Immediately; `failed` is not auto-refunded |
| Return | No separate returned state in the docs; a payout ends `failed` or `refunded` | Treat per the final status, check the payout record | If a recipient reports a return after `completed` |
| Refund | Payout ends `refunded` | Credit the platform balance back, ask for a corrected account | If the account was valid |

Minimums per rail, from the docs: 10 USD sender amount on most rails, 100 USD requested amount on SWIFT, 50 USDC or 40 EUR on SEPA, and 70 USDC or 50 GBP on Faster Payments. Validate these in intake so below-minimum rows never reach the queue.

A refund returns the stablecoins to the quote's refund wallet, or to the sending wallet when none is set. Fiat refunds depend on the banking network. See the [cut-off times reference](/docs/kb/cut-off-times) for refund timing.

## How do platforms earn on payouts?

Through partner fees: a percentage in basis points, a flat amount in cents, or both, configured once and referenced per quote. The collected amount is `partner_fee_amount` on the payout quote.

Create the fee configuration, then pass its `partner_fee_id` on each payout quote. Fees accumulate across the calendar month and are released on the first day of the next month, net of your BlindPay invoice, per the [partner fees docs](/docs/learn/partner-fees). There is no update endpoint, so change a fee by deleting and recreating it.

One detail matters for monitoring. The webhook table on the payouts page lists a `payout.partnerFee` event, but the partner fees doc says `payin.partnerFee` and `payout.partnerFee` are declared in the catalog and do not currently fire. Do not build revenue accounting on that event. Read `partner_fee_amount` from the quote and store it on your ledger line at execute time. The [partner fees and markup guide](/resources/more/partner-fees-markup-on-live-quotes) shows the pattern.

On pricing, BlindPay states: "BlindPay combines a monthly plan (platform access) with per-transaction fees that vary by rail and network and are disclosed only in the live quote before execute. BlindPay does not publish a static flat rate card; always cite the quote's fee breakdown rather than a fixed bps number." There is no setup fee or monthly minimum, per the [billing docs](/docs/learn/billing).

## What should you monitor in production?

Monitor the gaps between states, not only errors. A payout stuck without an event is the failure that costs most.

1. **Payouts without a terminal status** after the rail's documented ETA plus margin: Pix, SPEI and Transfers are instant, TED, SEPA, wire and ACH COP take about 1 business day, ACH and SWIFT about 2.
2. **Payouts in `on_hold`** beyond your own threshold, since holds can last up to 30 days.
3. **Webhook delivery failures.** Endpoints are Svix-signed; the hosted webhook portal shows delivery attempts and lets you replay events.
4. **Quote expiry rate.** A rising share of expired quotes means your queue is too slow.
5. **`failed` and `refunded` rate** per rail and per country.
6. **Funding wallet balance** against the queued total.
7. **Duplicate execute attempts.** Look for `409 idempotency_key_in_flight` and `Idempotency-Replayed` headers.
8. **Ledger drift:** sum of completed `receiver_amount` against your payment records.

The [webhook reconciliation guide](/resources/more/stablecoin-api-webhooks-reconciliation) covers signature checks, duplicates and ordering, so an old event never moves a payout backwards.

## How do you keep recipients compliant at volume?

Verify before the first payout and keep records. Every payout flows through a customer that completed KYC, and every payout needs an approved bank account. Your platform collects the data, and BlindPay verifies it. Third-party bank accounts are allowed, with `recipient_relationship` set to something other than `first_party`. See [first-party vs third-party payouts](/resources/more/first-party-vs-third-party-payouts) and [stablecoin payout compliance](/resources/more/stablecoin-payout-compliance).

For the local rails behind this architecture, the Central Bank of Brazil describes [Pix](https://www.bcb.gov.br/en/financialstability/pix_en) and Banco de México describes [SPEI](https://www.banxico.org.mx/services/interbanking-electronic-payme.html). For US ACH timing, see [Nacha on Same Day ACH](https://www.nacha.org/same-day-ach).

## Methodology and sources

External sources: [Central Bank of Brazil on Pix](https://www.bcb.gov.br/en/financialstability/pix_en), [Banco de México on SPEI](https://www.banxico.org.mx/services/interbanking-electronic-payme.html) and [Nacha on Same Day ACH](https://www.nacha.org/same-day-ach). BlindPay docs used: [payouts](/docs/payouts), [payout quotes](/docs/payout-quotes), [bank accounts](/docs/bank-accounts), [cut-off times](/docs/kb/cut-off-times), [partner fees](/docs/learn/partner-fees), [webhooks](/docs/learn/webhooks), [webhook events](/docs/learn/webhooks-events), [idempotency](/docs/learn/idempotency), [sandbox vs production](/docs/learn/sandbox-vs-production) and [billing](/docs/learn/billing). The architecture is a design pattern, not a BlindPay product. No batch endpoint, rate limit or state beyond those documented is assumed. Facts checked as of October 2026.

## What to do next

Read the [webhook docs](/docs/learn/webhooks), register an endpoint on a free development instance, and run 50 test payouts with the 66600 and 77700 amounts before you queue real money.
