Skip to main content

Inbound fax receive-side workflow

The Outbound fax workflow guide covers sending; the Inbound fax routing guide covers the per-DID routing knobs. This guide operates the receive side as a whole: how an inbound fax is addressed and deduplicated, what events it emits, how routing decides its destinations, how inbound failure modes differ from the outbound T.38 vocabulary, when a tenant-attached Telnyx fax connection changes the receive pipe, and the guardrails that keep junk fax traffic out of your inbox. You will:
  1. Understand billing on the receive side — and why inbound costs read differently on the CFO dashboard
  2. Walk the receive-side ledger — the message row behind every inbound fax
  3. Anchor the contract — inbound receipts are free, outbound bills on success
  4. Consume the receive-side event — the message.received payload
  5. Diagnose receive-side failures — the receive vocabulary is not the outbound T.38 vocabulary
  6. Attach your own carrier connection when the platform default is wrong
  7. Run the production checklist

Prerequisites

  • A fax-capable number on your account (see Buy numbers — inbound fax works with the same inventory).
  • An API key from Settings → API Keys (a dv_live_sk_… live key) for the routing calls below.
  • A public HTTPS endpoint if you want your own webhook consumer in addition to email/inbox routing.

1. Billing on the receive side

Outbound fax is on-success-charge: the wallet is billed only when the carrier confirms a successful transmission, and failed attempts (busy, no answer, no fax tone, poor line quality) are never billed — the full mechanics are in the send-side guide. Inbound fax is the inverse: receiving a fax is a no-charge event. There is no wallet deduction and no per-page meter on the receive side; the cost you see on the CFO dashboard for inbound fax is the number-inventory cost of the DID, not per-fax usage. That asymmetry matters when operators read the billing ledger for the shared fax surface:
  • An outbound delivered event is the one that carries the charge; an inbound message.received event never does.
  • Reconciliation of the spend report therefore separates on one flag: direction: "outbound" rows bill, direction: "inbound" rows do not.
  • A tenant-attached carrier connection (section 6) changes who owns the receive pipe, not the zero-charge property — inbound remains free either way.

2. The receive-side ledger

Every inbound fax is persisted as a messages row with channel: "fax", direction: "inbound", the sender’s number in from, your DID in to, and the provider media URL in media_url. The row is the audit record of the receive; the PDF copy forwarded to email, the inbox ticket, or fetched by your webhook consumer is the durable artifact. Walk the ledger like this:
  1. Addressing decides persistence. The provider’s fax.received webhook posts the sender (from), the DID (to), the media URL, and a provider fax_id. Orbit resolves the tenant from the to DID. A DID no tenant owns drops at this step; a DID owned but unrouted persists with no downstream delivery.
  2. The dedup marker. The provider fax_id is the idempotency key. A carrier retry of the same fax.received event creates exactly one message row — carrier retries are expected, not duplicates.
  3. Routing fans out once. If routing is enabled, the PDF is fetched in a bounded fetch and fanned out to the configured destinations (email, inbox, or your webhook consumer).
For per-DID config the surface is:
with the same { enabled, forward_emails, deliver_to_inbox, archive_path } shape the routing guide documents. This guide’s addition to that loop: the record exists regardless of routing — enabled shapes the human surface, not the ledger.

3. The contract: inbound free, outbound on-success

The fax channel’s billing contract is direction-scoped. From the operator’s side of the ledger: Read the wallet ledger against that table: an outbound charge without a matching delivered event, or any charge on an inbound event, is the anomaly to escalate.

4. Consume the receive-side event

Every inbound fax arrives on your tenant event bus as message.received with channel: "fax", whether or not per-DID routing is enabled. The envelope:
Three fields drive a receive-side consumer:
  • data.from — the sender’s number (screening input, see section 5).
  • data.to — the receiving DID (addressing input, and the key your routing decision should match).
  • data.metadata.media_url — the provider-issued PDF link. Presigned and temporary: fetch it promptly or rely on the email-forward/inbox-ticket copy of record.
Consumer discipline for this event: verify X-Orbit-Signature, reject deliveries older than five minutes, return 2xx fast, and dedup on the event id — the recipe in Webhook security applies verbatim.

5. Receive-side failure diagnostics

The outbound side’s failure vocabulary is the carrier’s T.38 diagnostics — busy, no answer, no fax tone, poor line quality, handshake rejection — passed through on message.failed. The receive side never sees those codes: an inbound fax either lands or the receive pipe has an operational failure of its own. The failure classes to route on for the receive side:
  • Signature rejected — Telnyx’s signature failed verification; the webhook returns 401 and the fax is counted but never stored. Fix the carrier public key, or check for a proxy stripping signature headers.
  • Unresolvable DID — the to number does not resolve to a tenant; the fax drops. Audit which DIDs are published as fax-reachable before this bites.
  • Routing write-time rejection — a bad forward_emails entry or more than 20 recipients fails with 422 at write time; fix the config before traffic lands.
  • Media fetch failure — the bounded PDF fetch timed out or returned non-2xx; email recipients get the link only. Delivery is still counted; the artifact is degraded, not dropped.
  • Junk screening (rate guard) — a sliding-window per-(tenant, channel, sender) flood guard throttles abusive inbound senders before storage: at the soft cap the pipeline persists and logs; at the hard cap it drops the inbound, audit-logs inbound.abuse_blocked, and returns 200 so the provider does not retry-burst. Redis-down fails open (allows). Content-classifier screening (which runs for SMS/WhatsApp/email/RCS) is channel-gated and does not yet score fax — build your own on the message.received consumer if the rate guard is not enough.
When a receive-side symptom does not match the classes above (a delivered fax with no message record, a record with no media URL), the Troubleshooting hub and the fax channel page error catalogue are the next hop.

6. Tenant-attached carrier connections

Receive-side traffic normally rides Orbit’s shared Telnyx fax connection — the same default the outbound send uses. Attach your own Telnyx Fax application when the platform default is wrong:
  • Compliance / data residency — inbound faxes must traverse a carrier account you control and audit.
  • Carrier-level features — a dedicated fax application with its own number inventory, routing rules, or webhook-secret rotation.
Set the org’s fax_connection_id in tenant settings. Resolution order is: tenant-level fax_connection_id → platform default → legacy voice connection fallback (the fallback logs a deprecation warning). The same resolution drives outbound and inbound symmetrically, so an org on a dedicated connection receives and sends over it.
Do not confuse connection choice with routing config. The connection decides which carrier account owns the receive pipe; the per-DID routing config (GET/PUT /api/v1/numbers/:id/fax-routing) decides where the received fax is delivered. They compose — a tenant-attached connection still honors per-DID routing for its DIDs.

7. Production checklist

Run through this list before a fax DID goes live:
  1. Routing enabled on the DID. enabled: true with at least one destination (emails, inbox, or your webhook consumer). A bare DID persists only the message record.
  2. Destinations validated. forward_emails addresses are deliverable — the 422 at write time catches typos; later per-recipient delivery failures are logged and never block other recipients.
  3. Junk screening understood. The flood guard is rate-based. If you need content screening, plan a message.received consumer that scores senders or OCR before humans see the fax.
  4. Connection decision made. If compliance or residency requires a dedicated Telnyx Fax application, set fax_connection_id before publishing the DID — traffic on the shared connection does not re-resolve later.
  5. Consumer discipline (when you consume message.received yourself): signature verification, five-minute replay window, fast 2xx, dedup on event id.
  6. Retention expectations set. The message record is the audit trail; the PDF copy forwarded to email or the inbox (or fetched by your consumer) is the durable artifact. Treat the provider media URL as temporary.