Skip to main content

Connect a SIP trunk

A SIP trunk connects your own phone system to Orbit. This guide walks the full lifecycle: decide the direction, create the trunk, validate and test it, wire it to a DID, then keep an eye on registration and route quality. Everything here also exists in the dashboard under Settings → SIP trunks.

Step 1 — Pick the direction

Orbit supports two trunk directions: Incoming trunks authenticate your PBX on every call, by source IP, digest credentials, or both. Outgoing trunks carry your carrier’s SIP host and the register credentials the carrier issued you. Prerequisites
  • For outgoing trunks: the SIP host, port, and (if your carrier registered your account) username/password from your carrier. For US outbound traffic, Orbit applies STIR/SHAKEN attestation to the caller ID — see STIR/SHAKEN.
  • For incoming trunks: your PBX must be able to send SIP INVITEs to a public domain over UDP 5060, TCP 5060, or TLS 5061, and either present a stable source IP or answer digest challenges.
  • To place calls, own at least one DID — buy one under Numbers → Buy numbers or via the Numbers API.

Step 2 — Create an outgoing trunk

Outgoing trunks are the ones you use for dialing out through your own carrier.

Validate before you save

Probe first — the probe runs a real SIP REGISTER against the carrier host and returns the exact step that failed (DNS resolution, TCP/TLS connect, or the REGISTER itself) so you fix it before persisting anything:
A reachable: false response names the phase that failed and gives a sanitized operator message — the dashboard’s Add trunk dialog runs this same probe before it lets you save.

Create the trunk

Credentials are encrypted at rest. Optional fields: codecs, maxConcurrent (per-trunk concurrent-call cap, 1–2000), failoverTrunkId (another trunk Orbit falls over to when this one drops registration), and enabled (default true). If you ran the probe first, pass its verdict as probe_status (registered | unregistered | unknown) so the row starts with an accurate badge instead of a guessed one — the dashboard does this automatically.

Step 3 — Create an incoming trunk and capture the secret

Incoming trunks let your PBX originate calls into Orbit.
authMode is one of:
  • digest — the PBX answers digest challenges with the issued username/password (handles SBCs with unstable egress IPs).
  • ip_allowlist — only INVITEs from allowedIps are accepted (no password exists; nothing can be rotated).
  • both — the PBX must match the allowlist and answer digest.
A few hard guards apply on the allowlist:
  • Each entry must be a valid IPv4/IPv6 address or CIDR, and at most 50 entries per trunk.
  • 0.0.0.0/0 and ::/0 are rejected — a catch-all would disable the allowlist. Use authMode: "digest" if you don’t want IP filtering.
  • authMode: "ip_allowlist" or "both" requires at least one entry.

Capture the digest secret — it is shown exactly once

The create response contains the plaintext digestPassword once, with credentials_revealed_once: true. Every read of the trunk afterwards shows the password masked as ***. Save the username and password into your PBX immediately, or rotate later:
The reveal token is single-use and expires after five minutes — a replay returns 410 GONE, and rotation only applies to incoming trunks with digest auth. Calls already established under the old password keep running; the new password applies from the next INVITE — update your PBX before then to avoid a 401 burst.

Verify go-live readiness before routing traffic

Before you point calls at the trunk, run the readiness check. It is a configuration verdict, not a network probe — incoming trunks never send a REGISTER (your PBX initiates INVITEs toward Orbit, so “registered” does not apply the way it does to outgoing trunks). The check confirms every auth gate your authMode requires is satisfiable:
  • acl_configured — every allowedIps entry parses as a valid IP or CIDR (required when the auth mode is ip_allowlist or both).
  • credentials_present — digest username and password exist (required when the auth mode is digest or both).
  • auth_path_ready — all required gates pass together.
On the first clean pass the trunk is stamped onboarded_at — the go-live marker the dashboard onboarding wizard closes on. A blocked run returns HTTP 200 with status: "not_ready" and the exact gates still failing in issues, and that blocker is written onto the trunk so the dashboard health surfaces show it until you re-verify. Two rules keep the stamp honest:
  • Every later trunk edit clears onboarded_at — changed the allowlist or credentials after onboarding? Re-verify; the stamp can never describe a stale configuration.
  • Reset restarts the wizardPOST /api/v1/voice/sip-trunks/{id}/onboarding/reset clears the stamp and checklist without touching your auth config, for when you want to re-run the flow after changing the ACL outside the wizard.
Both endpoints apply to incoming trunks only; on an outgoing trunk they return 422. Readiness past go-live (auth failures, rejected INVITEs) is covered by the registration polling and health snapshot in step 8 — verify only gates whether the configuration is complete enough to carry traffic.

Step 4 — TLS and SRTP posture

Your carrier-facing config and your softphone-facing config have two different security baselines: Prefer tls on trunks when your carrier offers it — the create handler’s pre-save probe runs against the transport you pick, so a TLS path that cannot complete the handshake fails the probe and never becomes a broken saved row. On the credential side, every issued credential reports sip_srtp: "Mandatory" in its connection details; a softphone that advertises RTP-only media will not establish audio.

Step 5 — Point your PBX at Orbit

Configure your PBX with the digest digestUsername and password from the step above and the SIP edge host shown in Settings → SIP trunks in the dashboard. For authMode: "ip_allowlist" or "both", list every IP your SBC can originate from — a burst of unauthorized attempts shows up in the auth-failures ledger (step 8).

Step 5 — Wire routing to a DID

Calls arriving on an incoming trunk enter Orbit with a dialed number; the DID’s inbound route decides where the call lands. Set it per number:
Pick a route type per DID: webhook (your HTTPS URL returns call instructions), agent (an AI voice agent), sip_forward (send the call to a SIP URI or back through a trunk), queue, ivr, voicemail, softphone_user, or decline. See the inbound routing reference for the full set and config shapes. Two of those route types deserve a second look in a trunk setup:
  • sip_forward to an external AI voice agent — point the DID at a third-party AI provider’s SIP endpoint (callers.ai, Retell, Vapi, or a self-hosted PBX). Orbit terminates the carrier media and re-INVITEs your provider over TLS, so an inbound-trunk call can land in an AI agent without you running any SIP infrastructure. Full walkthrough: AI voice agents via sip_forward.
  • Inbound to registered softphones — when the trunk carries calls you want to ring desk phones or softphones directly, bind a SIP credential per device (/api/v1/sip-credentials): each issued credential registers against the SIP edge (sip_server in the response) and is routed on inbound DIDs of type softphone_register. Options on the credential include a per-device source-IP list (allowedIpCidrs), the outbound caller-id mode, a daily spend cap, and busy/no-answer/offline forward targets. That is the full cycle for a device: claim the credential, restrict its source IPs, and feed it inbound calls via routing.
For incoming trunks, optional routingRules on the trunk itself apply before the DID route — a countryBlocklist or destinationPrefixBlocklist blocks matching destinations, and callerIdRewrites swap the presented caller ID per destination country or prefix. Simulate the fixed evaluation order against any destination before going live:
executionOrder lists the routing controls in the order dispatch applies them and which ones you have configured; simulation (when you pass a destination) shows which control wins and which caller-ID rewrite applies. The simulation is read-only — it mirrors dispatch exactly.

Step 6 — Test the trunk

Validate configuration or re-check registrationPOST /:id/test. On outgoing trunks this runs the same REGISTER probe as step 2 and records the verdict on the trunk (it also stamps lastRegistrationCheckAt, which the dispatch gate consults). On incoming trunks it validates the saved config (allowlist entries, digest presence) and returns status: "ok" | "misconfigured" with an issues list:
The registration verdict is one of three values:
  • registered — the last check completed a REGISTER successfully.
  • unregistered — the check ran and the registrar rejected or timed out; read lastRegistrationError on the trunk for the recorded reason.
  • unknown — no check has run yet, or the verdict was acknowledged as uncertain at creation.
Place a real call through the trunkPOST /:id/test-call dials a destination over the trunk and records the billable result. The destination must be an E.164 number the org owns or an Orbit platform test number; emergency-shaped dials are rejected with EMERGENCY_CALLING_NOT_SUPPORTED:

Fax over the trunk (T.38)

Inbound fax shares the same inbound edge as voice: a fax sent to a DID arrives as a fax.received event and follows the DID’s fax routing to a webhook, email recipients, or the inbox. If you carry fax on the same trunk, keep the codec list on the trunk compatible — T.38 negotiation is driven by the carrier side, and a mismatched codec set can force unreliable audio fax. See Inbound fax routing for the receive-to-delivery lifecycle and the per-DID routing contract.

Step 7 — Emergency dialing

Orbit voice does not support emergency dialing (911/112/999/…), and every voice surface rejects emergency short codes. For US numbers, still register a dispatchable E911 address per DID — it records the location a regulator expects to find even though Orbit’s posture is block-and-disclaim:
See Emergency address for field validation rules.

Step 8 — Monitor

Health snapshot

Ranked attention-first, with severity counts — the same numbers the SIP trunks page badges:

Route quality

ASR/SDR-style scoring plus capacity bands per outgoing trunk over a 24h or 7d window — read this before blaming a carrier: a saturated concurrent-call cap shows up as blocked, not failed:

Usage per trunk

Seven days of daily totals by default, or a 24-hour attempt/accepted/ rejected breakdown with the top rejection reason on incoming trunks:
For incoming trunks, failed digest/allowlist attempts have their own seven-day ledger — the first place to look when calls stop arriving:

What the platform does on its own

Every enabled outgoing trunk is re-registered on a cadence of roughly five minutes: a real REGISTER with the stored credentials, with the verdict persisted back onto the trunk. If a check streak of transient register failures accumulates, the trunk is flagged rather than silently retried forever. Meanwhile a dispatch gate runs on every outbound call: a trunk whose cached registration verdict is anything other than registered (or whose verdict is stale) stops being offered new calls, and the call walks the trunk’s failover chain — the backup trunk you set via failoverTrunkId — landing on the Devotel default outbound path if no healthy failover exists. Your calls keep completing; the trunk row keeps the recorded error until you fix it.

Production checklist

  • Outgoing trunk probed before save; verdict carried onto the row via probe_status
  • Incoming trunk authMode chosen; allowlist entries are specific IPs/CIDRs, never 0.0.0.0/0
  • Digest password captured at create (or rotated + revealed once) and stored in the PBX — the plaintext is never shown again
  • PBX pointed at the SIP edge host from Settings → SIP trunks with the issued digest credentials
  • DID inbound route set (PUT /numbers/+E164/routing) to a webhook, agent, queue, or SIP target
  • routing-analysis simulation run on a few representative destinations before sending traffic
  • test returns registered (outgoing) / ok (incoming); test-call completed media end to end
  • Trunk transport matches your carrier’s TLS offer, and softphone credentials show sip_srtp: "Mandatory" in every UA
  • Any fax DIDs confirmed codec-compatible on the trunk; inbound fax routing validated with a test fax
  • E911 dispatchable address registered per US DID
  • failoverTrunkId set on trunks that carry traffic, so the dispatch gate hands calls to a backup when registration drops
  • Health + route-quality surfaced in your monitoring (poll .../health and .../route-quality?window=24h)

When something breaks

Work through Troubleshooting: SIP trunk down or calls failing — the four most common recorded causes are DNS failing to resolve the host, a firewall blocking the SIP port, digest username/password mismatch, and codec/TLS mismatch on the REGISTER handshake. For a symptom-first checklist see also SIP trunk troubleshooting.