Stripe Treasury: embedded banking without becoming a bank
Embedded banking sounds like a product feature and is really a regulatory and accounting problem wearing an API. Stripe Treasury lets you offer balances, transfers, and payouts without holding a banking license — but the moment real money moves through your system, the interesting work is all in the parts the user never sees.
The pitch is simple: your platform gets programmable financial accounts, and your customers get something that behaves like a bank account. The reality is that you’ve signed up to be the source of truth for money you don’t physically hold, reconciled against a provider that tells you what happened after it happened, asynchronously, sometimes twice.
The money map
Before any code, you need a map: every place money can sit and every edge it can travel. Platform account, connected accounts, financial accounts, external (real-world) bank accounts. Funds flow platform → connected on payouts, external → financial on inbound, financial → external on withdrawals, and every hop has a fee, a hold, and a failure mode.
I model this explicitly as fund flows rather than letting it live implicitly in handler code. A transfer is a first-class thing with a source, a destination, a state, and a parent transaction — not a side effect of “calling the API.”
type FundFlow = {
id: FlowId;
from: AccountRef;
to: AccountRef;
amount: Money; // branded minor units — never a bare number
kind: "payout" | "inbound" | "withdrawal" | "reserve";
status: "initiated" | "posted" | "failed" | "reversed";
};
If you can’t draw the map, you can’t reconcile it. And reconciliation — not the happy-path transfer — is the actual product.
Received credits and debits are async truths
Money landing in a treasury account doesn’t arrive as a function return. It arrives later, as a webhook, and that webhook can mean several different things: an approved credit, a payout that completed, a credit that failed, a transfer that has to be reversed. Each meaning needs distinct income recognition and accounting.
So the consumer can’t be a switch over “event type” — it has to classify meaning and route to a use-case per meaning, each one a pure, testable predicate rather than a branch in a 300-line handler. That classification is where correctness lives; the actual ledger write is the easy part.
Payouts, holds, and expedited ACH
The thing customers feel is payout speed, and that’s where the architecture gets opinionated. Standard ACH is slow and free-ish; expedited ACH is fast and costs. Whether a merchant is eligible depends on risk, history, and reserve — a policy decision that has to be expressed once and reused, not re-derived in three places.
I keep the policy (who can be paid out, how fast, with what reserve held back) separate from the mechanism (the scheduled batch that discovers eligible merchants, fans out, and reconciles). The batch knows nothing about eligibility; the use-case knows nothing about cron. That seam is what lets you change the risk policy without touching the orchestration, and test each in isolation.
The failure modes decide everything
Here’s the part that flips the design: in a normal CRUD app you architect around the happy path and bolt on error handling. In money movement you architect around the failures, because they’re the expensive, irreversible, regulator-visible events.
- A credit fails after you optimistically recognized income → you need a reversal path, not a stack trace.
- A payout webhook arrives twice → idempotency keys make the second a no-op.
- A provider rejects a transfer non-retryably → that has to be tracked and surfaced, not retried into the void.
- A schema drifts under a parked event → versioned events and parse-don’t-cast at the boundary.
Embedded banking isn’t “move money fast.” It’s “always be able to say, to the cent, where every dollar is and why” — even at 3am, even after a bad deploy.
Get the map, the async classification, and the reversal paths right and the demo-able features — balances, transfers, payouts — fall out almost for free. Get them wrong and no amount of happy-path polish saves you, because the failure is the job.
Filed under systems. Building on Treasury or a similar rail? Compare notes.