Every Organiser, Their Own Stripe Account
Four payment flows, one reconciliation. What it actually takes to run multi-tenant payouts, refunds and chargebacks in an Eventbrite-shaped marketplace, and what I would do differently now.

UBMe is an event marketplace, similar in shape to Eventbrite. Organisers create events, sell tickets, and get paid.
I owned the backend and its architecture for several years. The part that took the most care was not the ticketing page. It was the money. Every organiser on the platform had their own Stripe account, so every payment had to be split correctly, every refund had to reverse the right split, and every payout had to reconcile against what Stripe actually settled.
What the platform had to do
The marketplace never sold on its own behalf. Each organiser sold their own tickets under their own name and received the money into their own Stripe account. The platform took an application fee.
That one requirement decides most of the backend, because it turns a single payment into several obligations. The customer must be charged once. The organiser must receive the right amount. The platform must keep the right fee. All three have to still agree weeks later, when someone asks for a report.
Onboarding decides whether an organiser can be paid
An organiser cannot receive money until Stripe knows who they are: identity, business details, bank account, and the capabilities that determine whether they can accept card payments and receive transfers at all.
It is tempting to treat onboarding as a form that is completed once. It is not. An account can exist and still be unable to accept a charge. Requirements arrive in layers, some at signup and some later, and some become past due when a document expires or a volume threshold is crossed.
So onboarding state was part of the domain model. Each organiser carried their capability state, their outstanding requirements, and the deadline attached to each. The platform refused to publish a paid event for an organiser who could not take money, and it said why in plain language instead of surfacing a provider error code.
The charge, the fee and the transfer
We used Stripe Connect with destination charges and an application fee. The platform creates the payment, the funds land in the organiser's connected account, and the platform keeps a defined fee. Split payment becomes a property of the transaction, so there is no separate transfer job to fail on its own, and there is one place to reconcile totals.
The trade-off is that the fee and the transfer belong to the same object. Any later change to the payment has to account for both.
Refunds and chargebacks
A refund is three decisions behind one customer action. The customer is refunded, the transfer back from the organiser is reversed, and the platform's fee is either returned or kept. If those three are not written down as a single flow, the arithmetic drifts. A small error per refund is invisible until somebody reconciles.
Chargebacks arrive through a different door, with a deadline attached, and they unwind the transfer as well. Both were modelled as money flows that reverse the original split, not as status changes on an order. An order status can change without money moving, and that gap is where the damage happens.
Duplicate deliveries
Stripe retries. The same event arrives more than once, and a handler that is not idempotent turns a duplicate delivery into a duplicate ticket, a duplicate payout record, or a credit applied twice.
Verifying the webhook signature is only half of the problem. A valid signature tells you the request came from Stripe. It says nothing about whether you have already processed that event. Those are two separate controls and both need an answer.
The mechanism was boring. Every inbound event was stored with its own identifier under a unique constraint, in the same transaction that applied the state change. A replay became a no-op. If the process died between storing and applying, the event still existed and could be processed again. If it arrived a second time, the constraint rejected it before any business logic ran.
Reconciling against Stripe
Webhooks tell you what arrived. They do not tell you what you missed. An endpoint deployed at the wrong moment, a handler returning errors for an hour, a queue that backed up: each leaves a hole, and the hole is silent because nothing turns up to announce it.
A scheduled job pulled transactions and payouts from Stripe and compared them against the platform's own ledger. Anything in Stripe that was missing from the ledger was created. Anything that did not match was flagged with the identifiers needed to investigate it. The revenue report organisers saw was built from the reconciled ledger, not from the webhook stream.
Payout schedules add a second clock. A payment can be charged, settled and paid out on three different dates, so an organiser's balance has to say which one it means. That distinction is easy to skip and expensive to explain later.
Notifications
Selling a ticket is half the work. The other half is that the right people find out: the attendee who needs their ticket, the organiser who needs to see the sale, the attendee whose session moved, the audience of a campaign the organiser just switched on.
That is fan-out. A single business event resolves to a set of recipients across channels, each with its own delivery rules. The layer I built subscribed to domain events across the platform and routed each one to the right channel and template, with four properties that mattered in production.
The delivery key was the business event plus the recipient plus the template, so a retry after a partial failure could not message the same person twice. Sends were queued and rate limited against the provider, batched, and separated by priority, so a payment receipt never queued behind a campaign. Failures retried with backoff and then went to a dead letter queue, where they stayed visible instead of disappearing. Switching a campaign off stopped the messages already queued, not only the ones not yet created.
Every send was recorded: what went out, to whom, and what came back. Without that record, the morning of an event stops being a debugging session and becomes an argument.
Check-in
Tickets were checked in at the door with a QR code and a geofence check, with validation on the server, rate limiting and replay protection. The requirement behind those controls is simple. An attendee who forwards a screenshot to a friend must not produce two valid entries, and a code that has already been used must not work twice.
What I would instrument first
If I built this again, the money path would be instrumented before the first feature, with every payment, refund and payout emitting one event carrying an identifier that is the same from charge to payout. Reconciliation then becomes a query instead of an investigation.
That is also roughly what I look at first when someone hands me a product with money moving through it. Whether one payment can be traced from charge to payout on a single page. Whether refunds and chargebacks are flows or buttons. Whether there is a unique constraint on the inbound event identifier. Whether reconciliation runs while everything is healthy, so there is a baseline to compare against during an incident.
None of it is exotic. It is the difference between a marketplace that can be operated and one that can only be demonstrated.
Most of the work I do now starts here: two weeks, fixed price, and a written answer you can use with or without me.
This was published first as Dispatches from the Workshop.
Subscribe on LinkedIn