Skip to main content

The two-phase authorize lifecycle

Most wallet charges on Orbit are synchronous — the platform rates the usage and debits it in one shot, and the ledger appends one row. That path fits any flow whose price is knowable before the money moves: a message send, a top-up, a per-minute voice settle. A second class of flow does not fit that shape: the number purchase flow, a KYC-gated order, a bulk capacity reservation, a conference event. In those flows the platform quotes a figure, then hands the order to a third-party leg that can succeed, fail, or sit in review for days. Pricing is exactly once — the flow either settles at the quoted figure or abandons it — and the danger is letting that flow tie up the wallet incorrectly in one of two ways:
  1. Charge at the end — the wallet race. While the third-party leg runs, other concurrent flows keep spending from the same balance; by settlement time the wallet may no longer cover the original quote. The quote said yes, the third-party leg succeeded, and the settle debit fails — an instance of the delivered-but-unbilled class.
  2. Charge at the start — the abandoned-order class. The wallet debits at the moment of authorization, but nothing gives the funds back when the order fails or times out: the balance sits locked for weeks until someone turns the operation into a manual refund request.
The two-phase authorize primitive exists to close both gaps with one contract: authorize first, then settle exactly one way. The lifecycle has three states and three transitions:
Authorize locks the funds so every other flow sees them as spent; from that point the flow ends in exactly one of two terminal outcomes — capture, which settles at or below the authorized figure, or release, which compensates the hold in full.

State machine: open, authorized, settled, released

Any multi-leg flow names its own hold id (for example the order id on a number purchase) and walks the same machine:
  • open → authorized — when the flow begins, the authorize phase places a solvency-checked hold for the quoted figure. Succeeds only if the wallet covered it at authorize time; a wallet below the quote refuses the whole flow before the third-party leg is ever dialed.
  • authorized → settled — when the third-party leg completes, the capture phase validates the final settled figure against the authorized figure. An attempt to settle for more than the authorized figure fails — if the carrier’s final price came back higher mid-flight, the flow releases this hold and re-authorizes rather than settling past the quote.
  • authorized → released — when the flow abandons (a compliance review that never closes, an order you cancel, a retry that established the reservation was no longer needed), the release phase credits back the authorized figure exactly once. This is the humane path that prevents the abandoned-hold money leak from lingering for months.
Only one terminal outcome applies per hold id. The authorize debit and its eventual capture-feeling-row or release credit are all visible in the append-only ledger — the same ledger the wallets, credits, and charges concept describes — so a finance reconciliation over the tenant’s own records sees the hold, one terminal movement, and no double-count.

Idempotency and the charge-key contract

Every authorize debit is keyed on the hold id with a hold:<holdId> idempotency key, and the release phase re-derives its own one-shot credit key from the same hold id. Two consequences matter for an integration:
  • Retry-safe authorize. If a network blip drops the authorize response but the debit went through, retrying with the same hold id collapses to the cached result and does not move the wallet again — the same retry semantics the idempotency and safe retries concept page guarantees for any balance mutation. Replayed authorize responses are flagged so your own code does not re-fire side effects (order creation, webhook emission, downstream leg submission) on a deduplicated authorize.
  • Exactly-once release. The releasing compensation also derives its idempotency key from the hold id, so a concurrently retried release collapses to one credit. Both the hold’s original debit and the release credit stay in the ledger — finance reconciliation sees both, not a silent refund.
The hold id itself is a caller-supplied slug naming the logical reservation (on a number purchase, the order id). The slug allows lowercase letters, digits, colons, hyphens, and underscores — a safe shape that keeps the derived hold:<holdId> key collision-free across flows.

Where this applies: multi-leg flows

Any flow whose funds must be locked before a long-running third-party leg completes is a candidate:
  • Number (DID) purchase — a hold is placed at order time so the exact inventory quote is locked while the carrier leg runs.
  • KYC order intake — an order with a compliance review step locks the amount while the review is in flight.
  • Bulk reservation — a capacity reservation holds the quoted figure while a bulk scan decides which members to commit.
  • Conference billing — a provisional authorize against a quoted ceiling while the event is live.
These are flow-level decisions a tenant makes. In every case the authorize phase validates solvency before the third-party leg ever runs, and the settle capture phase validates the final figure at or below the authorized upper bound.

How it differs from the synchronous charge

The synchronous debit that rates and charges a usage event in one call — the default debits described on wallets, credits, and charges — is correct whenever the platform can compute the price before money moves. The two-phase authorize is the alternative for the class of flow that cannot: If a flow has no long third-party leg — a message send, a per-min voice settle, an AI token charge — the synchronous path is the right tool and cheaper to administer. The two-phase authorize primitive exists precisely for the handful of flows where the quote escapes synchronous settlement.

Release: what an abandoned hold does

Two distinct failure shapes exist on the money path — the over-reserve-forever leak (charge up-front on an abandoned flow) and the under-reserve retroactive race (charge at settle time). The release phase exists to close the first; the authorize phase exists to close the second. What happens on the abandon path matters for ops:
  • A hold that abandons stays open on the wallet until the release phase runs. There is no automatic timeout sweeper — the flow owns the decision, and nothing about the platform guesses “the flow probably finished by now” into auto-compensation. If the third-party leg completed, the settle capture may still validate and clear the hold correctly even long after authorize.
  • The release credit compensates the original authorize debit exactly once, even under concurrent abandoned-order retries, so the wallet ends neither locked nor double-credited.
This page is the conceptual contract. The person-level flow walkthrough for the DID purchase path lives in the buying numbers guide; the ledger mechanics (idempotency, units, pause semantics) live on wallets, credits, and charges; the rating pipeline that chooses between the two authorize shapes lives on billing and wallet.