Skip to main content

Fax lifecycle reference

The Outbound fax workflow, Inbound fax receive-side workflow, Inbound fax routing, and Fax lifecycle orchestration guides each cover one leg of fax in depth. This page is the reference that ties them together: the state model a fax row moves through, the timestamp fields that record when it moved, the retry posture on both legs, the per-DID inbound routing configuration, and the webhook events your consumers see at each transition. You will:
  1. Read the lifecycle state model
  2. Read the timestamp fields on a fax row
  3. Follow the outbound path end to end
  4. Configure inbound routing destinations
  5. Consume the webhook events
  6. Use the audit trail for correlation
  7. Treat numbers as your fax address book
  8. Reconcile sends against terminal events
  9. Know the limits and where errors surface

Prerequisites

  • A fax-capable number on your account (see Buy numbers).
  • An API key from Settings → API Keys (a dv_live_sk_… live key).
  • Familiarity with the Fax channel page for the field reference and limits.

1. The lifecycle state model

Every fax row on your tenant moves through a small state machine. Outbound rows start at queued; inbound rows land directly in their terminal state because the carrier has already completed the transmission before Orbit sees the fax. Two properties of this model matter for anything you build on top of it:
  • failed is terminal and there is no platform retry state. Orbit never re-attempts a failed fax on its own. If you want a retry policy, you own it: consume message.failed and re-send (a re-send is a new message row with its own message_id). Section 3 gives a recommended cadence.
  • sent is not terminal for fax. It means the carrier completed its side of the handshake; the billing and terminal verdict is delivered or failed. Wait for the terminal event before you settle anything.
On the inbound leg the carrier may re-post its fax.received webhook (its own delivery retry to Orbit). Orbit deduplicates on the carrier fax_id, so a carrier retry never creates a duplicate message row and never re-fires message.received.

2. Timestamp fields on a fax row

Each fax row carries a timestamp per transition. A field is set once, the first time the row enters the matching state, and never rewritten afterwards; null means the row has not reached that state. A fully-settled outbound row as returned by GET /api/v1/fax/:id:
From these fields you can derive the durations that matter operationally: sent_at - created_at is queue wait, delivered_at - sent_at is transmission time, and a failed row’s failed_at - sent_at tells you how far into the handshake the attempt got. The list endpoint GET /api/v1/fax returns the same columns, cursor-paginated, so a dashboard refresh never needs the per-row call.

3. Outbound orchestration: request to terminal state

The outbound path runs six steps; only two of them are yours to trigger.
  1. Dispatch. POST /api/v1/messages/fax validates the document URL, the from/to numbers, and the quality setting, then returns 202 with status: "queued". Nothing is billed.
  2. Media fetch and render. The worker fetches your media_url and prepares the document for transmission. A document that fails to fetch or render fails the send before the carrier is involved.
  3. Carrier handoff. Orbit hands the document to the fax connection (the shared platform connection, or your dedicated one when fax_connection_id is set). external_id populates at this point.
  4. T.38 transmission. The carrier negotiates with the destination and transmits page by page. The row is sending.
  5. Verdict. The carrier confirms delivered (wallet charged once, idempotency keyed per send so a worker re-dispatch cannot double-bill) or failed (no charge, carrier diagnostic in error_code/error_message).
  6. Event emission. message.delivered or message.failed fires on your tenant event bus and, when you set status_callback on the send, POSTs to that URL too.

Retry policy

Orbit does not retry outbound faxes. A failed row stays failed; there is no background re-attempt and no state between failed and a new send. This is deliberate: fax destinations fail for reasons that resolve on human timescales (a busy machine, an office that closed), and an automatic immediate retry mostly re-fails and re-consumes line time. When you implement your own retry, a cadence that matches how fax lines actually free up: Two refinements before you re-send:
  • Downgrade quality on poor_line_quality, do not just retry. very_high → high → normal shortens the T.38 handshake and survives noisier lines. The trade-offs are documented in the Outbound fax workflow guide.
  • Do not retry no_fax_tone. The destination answered as a voice line; re-sending fax to a voice line never succeeds. Fix the directory entry instead.
Each re-send is a fresh message row with its own message_id and created_at, so your reconciliation (section 8) should group attempts by your business document ID, not by message_id.

4. Inbound routing: where a received fax goes

When a sender faxes one of your DIDs, the inbound path runs without any request from you:
  1. Carrier receive. The sender’s machine negotiates T.38 with the fax connection bound to the DID; the carrier assembles the received pages into a single PDF.
  2. Carrier webhook. The carrier posts fax.received to Orbit with the sender (from), your DID (to), the carrier fax_id, and a media_url for the PDF. Orbit verifies the carrier signature; a rejected signature returns 401 and stores nothing.
  3. Tenant resolution and dedup. Orbit resolves your tenant from the to DID and dedups on fax_id, so a carrier re-post creates exactly one message row.
  4. Ledger write. The row is stored with channel: "fax", direction: "inbound", and the carrier media URL. This happens whether or not routing is configured; the ledger is independent of the human surface.
  5. Routing fan-out. Orbit looks up the per-DID routing configuration and delivers the PDF to the configured destinations (below).
  6. Event emission. message.received fires on your tenant event bus regardless of routing, so a webhook consumer sees every inbound fax even with routing disabled.
Routing is configured per DID with GET and PUT /api/v1/numbers/:id/fax-routing, where :id is the number’s ID in your tenant inventory. The configuration:
The destinations are additive; enable any combination: GET /api/v1/numbers/:id/fax-routing returns the current config, or the disabled default for a DID that has never been configured, so you can render the “set up forwarding” empty state from it. A 404 means the number is not fax-reachable on your tenant; an enabled: false row means the ledger still persists inbound faxes but no fan-out runs. The full per-destination walkthrough, including the inbox ticket shape, is in the Inbound fax routing guide.

5. Webhook event reference

Three tenant events cover the fax lifecycle, all in the standard envelope with channel: "fax". Verify the X-Orbit-Signature header on every delivery per the polyglot signature recipes; the event catalogue and signature mechanics live under Webhooks and Webhook security.

message.delivered

Fires when an outbound fax settles as delivered. This is the billing moment.

message.failed

Fires when an outbound fax fails terminally. The carrier diagnostic is in data.metadata; no charge is applied.

message.received

Fires for every inbound fax, whether or not per-DID routing is enabled.
When you set status_callback on an outbound send, the same delivered/failed event also POSTs to that per-send URL; the signature recipe applies there too.

6. Tenant-scoped audit trail

Fax leaves two audit surfaces, both tenant-scoped:
  • The message ledger is the per-fax audit record. Every transition is recorded on the row itself via the immutable timestamp fields (section 2), so GET /api/v1/fax/:id at any later point reconstructs when the fax was queued, dispatched, and settled. There is no separate event log to join; the row is the log.
  • Configuration changes land in your organization audit log. A PUT /api/v1/numbers/:id/fax-routing writes an audit entry recording the new enabled flag, recipient count, inbox flag, and whether an archive path is set, attributed to the acting user. Your Settings audit log (and its export/webhook, where enabled) is where you answer “who turned fax forwarding on, and when.”
Correlation fields across the two surfaces: Because a re-send is a new message_id, “across retries” queries are queries across rows sharing your business ID, each row self-describing via its timestamps and terminal error_code.

7. Numbers as fax address books

A fax reaches you through a fax-capable DID, exactly as a call reaches you through a voice-capable one. The number is the address: senders dial it, the carrier binds it to your tenant, and the per-DID routing configuration (section 4) decides what happens on receipt. Outbound, the from on a send must be one of your fax-capable DIDs, and the number inventory shows fax capability per number. Practical posture for most organizations is to treat fax DIDs as a small, published address book: one number per receiving function (contracts, pharmacy, billing), each with its own routing configuration, rather than one shared number with one fan-out. Provisioning and capability flags are covered in Buy numbers; the channel-level model in Fax channel and Fax delivery model.

8. Reconciliation recipe

To confirm every send reached a terminal state, tail your webhook stream into a map keyed by message_id, then cross-check against the ledger. Billing only ever attaches to delivered, so the same reconciliation proves the charge set.
For anything unsettled returns, poll GET /api/v1/fax/:id before escalating: the record is readable from the moment the 202 returned, and a null external_id means the send is still queued rather than lost. The status variant GET /api/v1/fax/:id/status (cached about 10 seconds) cross-checks against the carrier once external_id populates. The billing-side reconciliation, including how fax charges appear in usage export, is in CDR, usage export, and billing reconciliation.

9. Limits and errors

Failure diagnosis beyond this table: the Fax channel error catalogue, Message failed troubleshooting, and the Troubleshooting hub.

Next steps