← halfin journalMar 24, 2026 · 10 min read
Playbooks

Closing 4,000 sellers a week without losing your mind

A marketplace finance lead walks through their batch-payout reconciliation, dispute window, and the single Postgres view they wish they'd built sooner.

DO
D. OkonkwoGuest post
Marketplace settlements playbook cover
playbooks · cover
photo · Marcin Jozwiak on Unsplash

Guest post by D. Okonkwo, head of finance ops at a marketplace platform that closes ~4,000 sellers per week.

I joined this company two years ago. The first thing I did was open the seller-payout pipeline. The second thing I did was close my laptop and go for a walk.

This is what I wish I'd known on day one.

The problem, in one sentence

A marketplace doesn't have a payroll. It has a settlement engine. Every week, 4,000 sellers expect a payout that's the right amount, on the right rail, on the right day, with a receipt they can match against their own books. Get any of those wrong and you have a support ticket. Get them wrong consistently and you have churn.

settlements · weekly
cover · placeholder
Settlement throughput, weekly

The first thing I built (and regretted)

I tried to manage the settlement pipeline as a series of CSV exports out of our backend. Every Friday morning, a script generated a CSV; finance reviewed it; it got uploaded to our payments rail; it ran.

Two problems showed up immediately:

  1. No idempotency. If the upload errored halfway, we had no clean way to know which rows had paid and which hadn't.
  2. No audit trail back to the seller's transaction record. A payout was just a row in a CSV; the link back to which orders it covered was a manual reconciliation.

We were one mis-keyed re-upload away from double-paying 4,000 sellers, and the only thing standing between us and that outcome was that I was nervous enough to triple-check every Friday.

The second thing I built

A single Postgres view. The shape:

seller_id  |  payout_window  |  gross  |  fees  |  refunds  |  net  |  rail  |  status

Every row joins a settlement window (the 7-day chunk we're paying out) to a seller record and to a rail decision (which network the seller's wallet wants). The view gets recomputed every Sunday night. Monday morning, finance reviews; Tuesday morning, the batch runs.

The Postgres view is the source of truth. The CSV export is a downstream artifact. Don't let the CSV become the source of truth.

postgres view · schema
cover · placeholder
The Postgres view, schematic

What we hand off to the rail

A single batch payout call with an idempotency key. The rail (in our case, halfin) handles the network-level fan-out, the per-seller status, and the webhook stream back to our system.

Our backend listens to payout.completed and payout.failed events and updates the view. Failed payouts roll into the next window; the seller's record marks the failure reason. The seller can see the status from their dashboard.

The dispute window

We hold a 7-day rolling reserve against any seller's pending payout. If a buyer disputes a transaction inside that window, we can claw back the disputed amount from the reserve before the payout settles. After the window closes, the funds are committed.

This was non-negotiable for our risk team. It also means the seller's available balance and their pending balance are two different numbers. The UI has to make that obvious.

reserve · timeline
cover · placeholder
Reserve and dispute timeline

The Postgres view I wish I'd built sooner

A second view, derived from the first:

seller_id  |  trailing_30d_gross  |  trailing_30d_fees  |  trailing_30d_disputes  |  health_score

Health score is a simple weighted formula. Sellers with high dispute rates or sudden spikes get flagged for manual review before the next payout. We cut our chargeback losses by 60% in the first quarter after this view shipped.

What I'd tell my past self

  1. Build the Postgres view first. Everything downstream is easier.
  2. Pick one settlement rail per region; don't sprawl. We support TRC-20 in APAC, ERC-20 in NA/EU, SPL for fast settlements. Three rails is enough; five is a maintenance burden.
  3. Make the dispute window visible to the seller. They will ask. Make the answer self-serve.
  4. Treat batch payouts as atomic. Either the whole window settles or none of it does.

D. Okonkwo

↳ end of articlehalfin journal · Mar 24, 2026