What actually happens during a payout
From the outside, a payout is one request: money leaves here, arrives there. From the inside, it is a small state machine with five stages, and every design decision in our API exists to make one of those stages boring. This is a walk through all five.
POST /v1/orchestrate
{
"source": { "kind": "crypto", "asset_id": "usdc", "amount": 1000 },
"destination": {
"kind": "fiat", "currency": "MXN", "recipient": { … }
}
}
You can price it first. POST /v1/orchestrate/quote returns what the source pays, what the destination receives, the rate between them and the fees — marked "indicative": true. The rate settles when the pay-in lands, and that is a feature, not a caveat. A quote is not a lock is the full argument.
1. requested: the request becomes a fact
The first thing we do is not move money. We validate the source and destination, record your idempotency key, and write the movement down. From this moment the request is replay-safe: if your process dies and retries, you get the same movement back, not a second one. Retries are the normal case in payments, not the exception, so idempotency is on every write rather than an option you enable.
The response tells you how to fund the movement — a deposit address for a crypto source, a bank account or mobile-money prompt for a fiat one — and the orchestration sits in awaiting_payin. Nothing has moved yet, and nothing will until the source leg does.
2. received: everything becomes dollars
A bank transfer, mobile money, or a digital asset: whatever came in lands and settles into a dollar balance at the hub, over a stablecoin rail you never touch. This is also the moment the rate stops being indicative — the money now exists, so there is finally something to convert. And it is the structural trick that keeps the integration small: with a hub in the middle, adding a market is one new edge, not a new row and column in a corridor matrix. How funds arrived has no bearing on how they leave.
3. processing: the routing decision
This is the stage the product is named for. The docs gloss it as converting and routing your funds; underneath, the router evaluates the ways out of the hub for your destination — which rails are available, what liquidity looks like, how each option settles, and what it costs — then commits to one path and fires the outbound leg from your balance. The deliberation is ours; the result is yours. You never learn which rail was chosen because you never have to care. That is the point of a routing layer.
4. sent: the shape of the outbound leg
The outbound leg executes on the rail the router chose. Here is the operational reality that shaped most of our design: on mobile-money rails, a single payout caps out at a few hundred dollars. Enterprise flow into these markets is not one wire, it is hundreds of thousands of payouts. At that shape, per-payout behavior dominates everything: retry logic, reconciliation, webhook volume, and cost all scale with the count, not the notional. An API designed around wires breaks here; one designed around counts does not.
If the outbound leg fails, the dollars are still in your balance — the pay-in settled there before the payout fired. You retry, or you withdraw. A failed delivery is an inconvenience, not a loss.
5. delivered: the record that outlives it
The destination confirms, and the movement closes. Every stage above fired a webhook when it happened, and the transaction carries the full history as an ordered timeline for as long as it exists:
requested · validated, idempotency key recorded
received · pay-in landed, dollars at the hub
processing · converting and routing
sent · outbound leg executing
delivered · destination confirmed
When something settles slowly, and somewhere in the world something always is, this timeline is what you read instead of opening a support ticket. We read the same one. The consumer Figo app runs on this API in production, so a slow rail shows up in our own product before it shows up in yours. How to render the timeline without lying to your users is its own article.
What we deliberately left out
There is no way to pick a rail, no per-corridor configuration, and no bilateral setup. Every one of those would push routing decisions back onto your integration, and the integration staying small is the product. You declare a source, a destination, and an amount. The engine does the rest, and the timeline proves it.
Questions, or a corridor you want mapped? hi@spendfigo.com, or start in the docs and the recipes.