Use case

Build a mass payout platform for affiliate networks on halfin

A mass payout platform has one job that has to be right every time: take a list of people who are owed money and pay each of them exactly once. The list arrives from somewhere upstream — an affiliate tracker, a partner ledger, a finance export — and the platform's whole reason to exist is turning that list into completed, reconcilable on-chain payments without ever double-paying a recipient or losing track of who already got paid. halfin gives you the primitive that makes this safe: a payout that carries a caller-supplied idempotency key, so the loop you build over your recipient list is interruptible, re-runnable, and auditable. There is no batch endpoint to learn — a mass payout is a fan-out over the single-payout API, and the idempotency key is what turns a fragile CSV script into a platform.

01

What a payout platform actually has to guarantee

When you build a system that pays a list of affiliates on someone else's behalf, the feature list looks short — accept a list, send the money — but the guarantees underneath are the hard part. The platform must pay each recipient on the list once and only once, even when the network hiccups, the process restarts mid-run, or an operator re-uploads the same file because they weren't sure the first attempt landed. It must keep going when one line is bad rather than stalling the whole run. And it has to leave a record precise enough that finance can close the period and a publisher who asks 'where's my money' gets a transaction hash, not a shrug.

The naive build is a loop over a CSV that reads a row, sends a transfer, and moves on. It demos well and fails in production for one reason: network calls return ambiguously. A send times out, and the transaction may already be on-chain even though your code never saw the confirmation. Retry it and you've double-paid; skip it and you've underpaid. On-chain payments are final, so both mistakes are discovered late — during reconciliation, or when a recipient quietly keeps an overpayment. A payout platform that can't tell 'already sent' from 'never sent' is a liability, not a product.

halfin closes that gap at the primitive level. Every payout you submit carries an idempotency key you supply. Submit the same key again — after a timeout, a worker restart, or an operator re-running the file — and halfin returns the payout it already created instead of making a second one. That single property is what lets you build a platform whose payout run is safe to retry from the top, which is exactly the guarantee a hand-rolled loop can never offer.

02

The fan-out: there is no batch endpoint

The design that surprises people building on halfin is that there is no `POST /payouts/batches`. A mass payout is a fan-out over the single-payout API: you walk your recipient list and submit each line to `POST /api/v1/payouts`, and the per-line idempotency key is what makes the loop a batch in any sense that matters. This is deliberate. A real batch endpoint would force you to reason about partial-batch state, retry semantics for half-applied batches, and a second status model layered on top of single payouts. The fan-out collapses all of that into one rule: every payout is independent, idempotent, and reconcilable on its own.

Each payout line is four fields — a currency (the crypto asset and network), an amount as a string, a destination address, and an idempotency key. The key is the whole trick, and it has to be deterministic: derive it from your own ledger so the same recipient in the same payout period always produces the same key. A natural shape is the affiliate's account id joined with the period, for example `payout-2026-06-affiliate_8842`. Because that derivation is reproducible, re-running the entire loop reproduces every key exactly, and halfin matches each one to the payout it already created — so the second pass converges on precisely one payout per recipient.

Payout line fieldTypeWhat your platform supplies
currencystringAsset + network per recipient, e.g. USDT (TRC-20 / ERC-20 / Solana), USDC (ERC-20 / Solana / Base), BTC, ETH, SOL.
amountstringDecimal as a string from your ledger. Never a JS number — money stays exact end to end.
destinationstringThe publisher's address; validated for the chosen network before the payout is accepted.
idempotency_keystringDeterministic per recipient + period, e.g. account id joined with the cycle. Re-submitting returns the original payout.
03

Partial failure is the normal case, not an exception

On a real publisher list, some lines are always wrong. An affiliate pasted a Tron address into a field your platform sends to Ethereum. An amount lands below a chain's dust threshold. A currency the destination wallet can't receive slips through. A payout platform that treats the first bad line as a fatal error and aborts the run is unusable at scale — one fat-fingered address can't be allowed to block two hundred correct payments.

Because the fan-out submits each line independently, a rejected line rejects only itself and is reported on its own. Your platform collects the per-line results, surfaces the rejected rows to whoever owns the run, and the rest of the payouts proceed. The fix is to correct the bad rows and re-submit the whole file. The lines that already succeeded are no-ops — their idempotency keys match payouts that already exist — and only the corrected rows create new payouts. There is no 'paid / not paid' column tracked by hand, and no run that strands itself halfway because row 240 was malformed.

Build your platform's run model around this. Treat each submission as 'reconcile this list against what halfin already has', not 'send this list'. A run is complete when every line has either a created payout or a reported, actionable rejection — and re-running a list that's already fully paid is a safe, cheap no-op rather than a double-pay risk.

  • Submit each recipient line independently; a bad line fails only itself.
  • Collect per-line results and surface rejected rows to the run owner.
  • Re-submit the whole corrected file — succeeded lines are idempotent no-ops.
  • Model a run as 'reconcile against halfin', not 'fire and forget'.
04

Keep staging and approval separate

A payout platform serves two very different actors: the integration that stages the run unattended, and the human who owns the money and signs it off. halfin keeps these apart by design. Submitting a payout does not move funds — each one enters a pending-approval state and is released from the dashboard. So the batch your platform POSTs is a proposal, not an irreversible action.

This is the property that lets you build a high-throughput programmatic path without handing it the keys to the treasury. Your service can fan out thousands of publisher payouts the moment the tracker's approved-earnings export lands, every line idempotent, every rejection reported — and nothing leaves a balance until a treasurer reviews and releases the run in the dashboard. For a platform that moves money on behalf of other affiliate programs, that separation is not a nicety. It's the control that lets one team operate the rails and another team own the funds, with an audit boundary between them.

05

A run loop you can build on

The core of a mass payout platform is a loop that's correct to retry. Take the recipient list your tracker already produces, derive a deterministic idempotency key per recipient and period, and submit each line. If the process dies mid-run, you re-run the entire loop: already-created payouts come back unchanged, and only the missing ones are created. Wrapping the whole thing in a retry is correct rather than dangerous — the second pass converges on exactly one payout per recipient.

The shape below is intentionally minimal so the idempotency property is visible. In a production platform you'd read the list from your ledger rather than a CSV, capture each response to reconcile per line, and verify the signed webhook before marking a payout complete — but the contract with halfin is exactly this call, repeated per recipient.

# A mass payout is a fan-out over the single-payout API.
# Each line is idempotent, so re-running the whole file is safe.
while IFS=, read -r account currency amount destination; do
  curl -sS -X POST https://api.thehalfin.com/api/v1/payouts \
    -H "Content-Type: application/json" \
    -H "X-API-Key: $HALFIN_API_KEY" \
    -d "{
      \"currency\": \"$currency\",
      \"amount\": \"$amount\",
      \"destination\": \"$destination\",
      \"idempotency_key\": \"payout-2026-06-$account\"
    }"
done < affiliates.csv
# Re-run after a crash: keys collide, nobody is paid twice.
# Released from the dashboard; settlement confirmed via signed webhooks.
06

Reconciliation: the part that makes it a platform

What separates a payout platform from a payout script is that it can prove what it did. The old CSV run leaves a spreadsheet of checkboxes that nobody trusts; a real platform leaves a chain of evidence per payout. On halfin each payout ties together four things: the idempotency key your platform supplied, a dashboard record, an HMAC-signed webhook, and an on-chain transaction hash. When a publisher disputes a payment or finance closes the period, the answer is concrete rather than reconstructed from memory.

Drive your reconciliation off the webhook, not the API response. halfin emits `payout.completed` when a payout settles, and your platform should verify the HMAC signature before it marks anything done — the webhook is the authoritative signal, and verifying it first is the rule, because a forged or replayed event must never flip a payout's state. Settlement is reorg-aware and uses per-chain confirmation thresholds, so a payout reported complete has actually settled to the depth that chain requires, not merely been broadcast. That matters most at the tail of a large run, where you want each publisher's status to mean 'done', not 'submitted and hopefully fine'. Schemas for the payout request and the webhook envelope live in the API docs at docs.thehalfin.com — build your reconciler against those, not against assumptions.

  • Tie every payout to an idempotency key, a dashboard record, a signed webhook, and a transaction hash.
  • Reconcile off the `payout.completed` webhook — verify the HMAC signature before acting on it.
  • Trust reorg-aware, per-chain-confirmed settlement over a raw broadcast.
  • Build against the published request and webhook schemas at docs.thehalfin.com.
07

Pay each publisher on the chain they actually use

A mass payout platform serving a global affiliate base can't force everyone onto one network — that just produces rejected payments and addresses that can't receive the asset you sent. Because the fan-out picks currency and network per line, a single run can settle a Tron-based publisher in USDT (TRC-20), a US-facing affiliate in USDC on Ethereum or Base, and a speed-sensitive partner in native SOL or USDC on Solana, all in the same submission. Native BTC, ETH, XRP, and EVM L2 / BSC tokens are available for publishers who want the base asset of a chain.

Let the recipient's preferred rail be a field on the line your platform stages, sourced from the publisher's payout settings, and the per-line model carries it through without any special-casing. The platform stays simple precisely because the chain choice is data, not branching logic.

related

Keep reading across the cluster.

Crypto payments for affiliate networks, CPA platforms, and webmastersPay affiliate networks and webmasters in crypto.Mass payouts: pay thousands of recipients in one idempotent batchPay thousands of recipients in one idempotent batchhalfin FAQ — how crypto payments work end to endHow halfin invoicing, checkout, deposit addresses, payouts, conversion, and webhooks work. Browse every question or read the cross-cutting answers.Affiliate payouts in crypto: idempotent runs, one approval, a clean recordIdempotent crypto affiliate payouts with one approval.Compliance for affiliate payouts: KYB, screening, and an audit trailKeep affiliate crypto payouts on a defensible process.Crypto payments for CPA networks: batch payouts and webhook reconciliationPay a large publisher base each cycle in crypto.Crypto payouts for gambling affiliates and iGaming partner programsPay iGaming affiliates revenue-share and CPA in crypto.Global affiliate payments: settle publishers in any country without bank railsPay publishers in countries your bank cannot reach.Instant affiliate payouts: settle on approval, not on the weekly bank cyclePay affiliates on approval, not the weekly bank cycle.Pay introducing brokers and affiliates from one idempotent batchPay introducing brokers and rebate partners in one batch.Affiliate network payouts for iGaming operatorsSettle iGaming affiliate networks in one auditable batch.Crypto payments for online stores and marketplacesAccept USDT, USDC, and Bitcoin in your store.