Skip to main content

Outbound pause and the resume propagation loop

When your wallet can’t pay for a send, Orbit refuses it with 402 and tells you why. This page is the recovery loop: which refusal you get, what to do for each block cause, and how a credit becomes “sending resumed” without waiting for a background tick. The flags taxonomy lives on the outbound pause gate and free-channel exemption; this page owns the credit-to-resume loop end to end.

When the wallet blocks a send

Two refusal classes share the 402 status, and GET /api/v1/billing/balance always names the state in outbound_paused / outbound_blocked and the cause in outbound_block_reason.
  • Pre-flight — INSUFFICIENT_BALANCE (402). The send itself can’t be paid for at the current balance. No pause flag is required — the wallet simply lacks the funds. Top up and resend.
  • Gate — SENDING_PAUSED (402). A soft pause the billing engine set. outbound_paused is true and outbound_block_reason names why: wallet_empty (auto-pause-on-zero), billing_alert:<id>:<name> (a spend or balance threshold you configured), or dunning_failed_3x (repeated failed payments).
  • Gate — SENDING_BLOCKED (402). A hard hold — subscription_suspended, payment_dispute_active, an admin suspension, or a block_outbound alert action. Nothing self-serves back into sending; the hold needs an update to the payment method, a dispute resolution, or support.
The response is authoritative both ways: send-time checks run server-side on every attempt, so a degraded balance read (degraded: true on the endpoint) can never lift a real pause.

The recovery loop per cause

Treat all three codes as terminal — never hot-retry a 402.
  • wallet_empty → top up. Any credit path re-arms the gate: card top-up, crypto top-up, auto-top-up, a refund credit, or a bonus/adjustment credit issued from the Billing console. The gate clears on the credit event itself (see the next section); free channels like WhatsApp keep sending even while the paid-channel pause persists (the exemption on the pause gate page).
  • billing_alert:<id>:<name> → resolve the alert. Topping up adds balance but does not silence the alert. Either trim the offending threshold under Dashboard → Billing → Alerts, or have an owner/admin call the flags-reset endpoint (POST /api/v1/billing/alerts/reset-outbound — the dashboard’s clear button calls the same route, and it clears both the outbound_paused and outbound_blocked flags the alert set). The flag drops as soon as the alert clears.
  • dunning_failed_3x → resolve dunning. Update the payment method; the payment-retry resolver clears the dunning hold only after a successful charge. A positive balance by itself never clears a dunning hold — the resume path acts on wallet_empty-marked pauses alone.

The shared resume propagation block

Every wallet-funding path — card top-up checkout, crypto confirmation, auto-top-up, refund credits, and operator-issued credits — runs the same resume routine when it lands a credit. The routine:
  1. Clears the wallet_empty pause flag with a reason guard — it touches only a pause the balance monitor marked wallet_empty. A billing-alert hold, a dunning hold, a suspension, or any admin set hold is left untouched.
  2. Busts the caches that mirror the pause flag — the send-time gate read and the balance-banner read — so the next send attempt and the next balance poll see the cleared state immediately instead of waiting out short cache TTLs.
  3. Invalidates the cached balance response so the next GET /api/v1/billing/balance reflects the fresh ledger.
Because the routine is fail-open, the credit still lands when the flag-clear errors — the balance-sync sweep (every few minutes) remains the backstop; the inline clear only closes the stale window from minutes to sub-second. This is also why an operator-issued bonus or adjustment credit behaves identically to a paid top-up: every path funnels through the same resume block, so no funding path can drift into “balance updated, gate still paused.”

Poll until the gate flips

After resolving the cause, poll until outbound_paused is false, then resume sending:
Recovering
Resumed
Poll on a moderate cadence (every few seconds) rather than hot-looping, and see send gating for the full recovery-style matrix. For hard blocks, outbound_blocked follows the same poll loop but the cause resolves in the dashboard or with support, not via a funding endpoint.

Why funding retry is safe

Resuming often means retrying a funding call that timed out mid-response. The top-up endpoints are idempotent per organization and event: the request’s idempotency key names the checkout session or payment reference, the result caches for 24 hours, and a retry replays the cached credit instead of moving money twice. The same idempotency contract covers the usage-debit side — inbound retried sends can’t double-charge the wallet either (ledger mechanics on wallets, credits, and charges). So “retry the top-up, then re-poll the balance” is always safe.