Skip to main content

SIP trunk first call: agreement to confirmation

Three guides cover SIP trunks on Orbit and each assumes the others. This walkthrough is the thread that joins them — it takes a first-time operator from the signed carrier/PBX agreement all the way to a completed, media-confirmed test call, and it names the exact gate at each step so you know the trunk is verified rather than assumed. Read this first, then keep the depth guides as reference: Connect a SIP trunk end to end for the full lifecycle surface, Connect a SIP trunk (BYOC) for the RTP, codec, and inbound-auth drills, and Register your PBX on Orbit for the outgoing-register deep dive and vendor config templates.

Step 1 — Validate: the pre-save checklist

Before a trunk row exists, run the agency checklist across the three surfaces that touch:
  • Carrier/PBX agreement. You hold the SIP host, port, transport, and (for a registering endpoint) the digest username/password the carrier or PBX issued. For an incoming trunk, you hold the exact static egress IPs you’ll originate from.
  • Ownership. Own at least one DID — buy it under Numbers → Buy numbers or via the Numbers API. A trunk without a DID can register but never carries inbound traffic.
  • Transport match (the SSL/ws decision). Decide now which transport the row saves: udp or tcp on port 5060, or tls on 5061. If your softphone/device side will use an Orbit SIP credential rather than a trunk, the returned connection details name the recommended transport (TLS, with SRTP mandatory) — browser- and app-based UAs can still carry the media on a secure WebSocket (wss) while the digest exchange runs over TLS. Pick before you save: the pre-save probe runs against the transport you chose, so a mismatch is caught in the dialog, not on the first live call.
Run the probe against your endpoint with the payload you intend to save — POST /api/v1/voice/sip-trunks/probe returns the phase that failed (DNS resolution, TCP/TLS connect, or the REGISTER itself) instead of persisting a broken row. The dashboard’s Add trunk dialog runs the same probe before it lets you save.

Step 2 — Configure the SSL/ws endpoint

For an outgoing (Orbit → your PBX/carrier) or an incoming (your PBX → Orbit) trunk, the transport you choose lands on the row as transport: udp, tcp, or tls. Prefer tls end to end when your carrier offers it — it encrypts the signalling path and sidesteps the NAT classes of failure that plague UDP. Ports follow convention: 5060 for UDP/TCP, 5061 for TLS. When the SSL surface is a softphone-facing SIP credential instead of a trunk, the returned sip_transport_recommended and sip_srtp fields name the exact posture — TLS on port 5061 with SRTP mandatory, and secure WebSocket available to browser UAs. Whichever way you go, the row on Orbit carries the posture that was probed at creation.

Step 3 — Authorize the SIP username/IP

An outgoing trunk carries the digest pair your PBX issues; an incoming trunk chooses one of three auth modes:
  • digest — the PBX answers challenges with the issued username and password (handles SBCs with unstable egress IPs).
  • ip_allowlist — only INVITEs from the listed IPs or CIDRs are accepted.
  • both — the call must match the allowlist and answer digest.
Authorize with specificity: up to 50 IPs or tight CIDRs per trunk, and never 0.0.0.0/0 — the catch-all would disable the allowlist, so the API rejects it. If you chose a digest mode, capture the issued digestUsername/digestPassword at create time — the password shows credentials_revealed_once: true, and every later read masks it as ***. Rotate later with the rotate-then-single-use-reveal pair if you missed the capture window.

Step 4 — Register and assign PBX routes

Register (outgoing trunks). The row must read status: "registered" on a real REGISTER exchange — Orbit re-polls every enabled outgoing trunk on a roughly five-minute cadence, so the row catches up to the fix you made at the far end. Use the POST /api/v1/voice/sip-trunks/{id}/test probe to re-check on demand; it stamps lastRegistrationCheckAt and the dashboard’s dispatch gate consults that verdict on every outbound attempt. Assign routes (all trunks). A trunk without an inbound route answers nothing: set the DID’s inbound route per number with PUT /api/v1/numbers/+E164/routing to one of webhook, agent, sip_forward, queue, ivr, voicemail, softphone_user, or decline. The route lives on the DID, not the trunk — the trunk governs auth and guardrails; the route chooses the destination. Option the trunk-side routingRules (country/destination-prefix blocklists, caller-ID rewrites, time windows) or the trunk-side auth path, and simulate the fixed evaluation order with the routing-analysis endpoint before sending traffic. If this is a softphone-bound route instead, bind a SIP credential per device (/api/v1/sip-credentials) and point the DID’s softphone_user route at the credential’s username.

Step 5 — Verify: TRACE/OPTIONS responses

Verify the path before live traffic with two surfaces:
  • The configuration verdict. POST /api/v1/voice/sip-trunks/{id}/onboarding/verify walks the auth gates your authMode requires and stamps onboarded_at on the first clean pass. A blocked run returns the exact failing gates in issues, and every later trunk edit clears the stamp, so re-verify after you change the allowlist or credentials.
  • The live media confirmation. POST /api/v1/voice/sip-trunks/{id}/test-call dials your destination over the trunk and records the billable result. For a SIP credential device, the verification is the UA’s reachability itself: issue an OPTIONS ping from your SBC (a qualify cadence) and confirm the Contact stays fresh in GET /api/v1/sip-credentials/{id} — the one TRACE-style response that proves the digest, transport, and binding are all holding.
After go-live, watch GET /api/v1/voice/sip-trunks/health — it ranks attention-first (down before degraded before active before unknown) and the dashboard Health tab groups on the same severities.

Step 6 — Advanced: failover, rate limits, inbound failover

  • Failover chains. Set failoverTrunkId on any trunk carrying traffic. When a dispatch finds the primary unregistered, Orbit walks the chain first and lands on the platform’s default outbound path if no healthy failover exists — chains are cycle-checked, and the fallback keeps the call down instead of dropping it.
  • Per-trunk limits. maxConcurrent (1–2000 per trunk), dailySpendCapCents, and hourlyCallCap on the incoming side are the guardrails the billing-hold verdict cites when a call is rejected on capacity.
  • Inbound failover. An incoming trunk’s first allowed IP is your own already-validated SBC address, so the same failoverTrunkId mechanism works for the inbound direction: the chain can hand a call to your own next hop rather than fail outright.
  • Routing rules. countryBlocklist, destinationPrefixBlocklist, and callerIdRewrites per trunk apply before the DID route; the simulation endpoint shows which control wins and which rewrite applies for any destination.

Step 7 — Troubleshooting: 401, 423, connection refused

Work through these in order — they account for nearly every recorded failure. 401 Unauthorized. The first 401 on REGISTER is the challenge step, not an error. A wall of 401s means the username/password on the trunk does not match what the PBX accepts, or the realm the PBX hashes against is wrong — and the domain must be a single unsplit token (the SIP server where packets go, versus the account’s digest identity domain, are different values and the PBX must hash against the correct one). Capture the altered password at create or rotate, and update both sides — a PBX-side rotation without a matching trunk update bursts 401s on the next call. 423 / rapidly-flapping registration. A softphone that re-registers on a pathologically short interval (roughly 8 seconds) can be granted a binding that lapses between refreshes — jambonz computes the granted expiry from the request with no minimum, so the AOR flaps offline on ordinary jitter. The register-auth webhook floors a too-brief requested expiry up to a healthy minimum and emits a register_expires_too_brief counter; on your side, set a sane interval (30 seconds or longer) and confirm the Contact stays fresh on the credential’s GET response rather than watching 423 responses. Connection refused / timeouts. Almost always one of three things: DNS failed to resolve the host (the probe names the phase), the firewall dropped the SIP port (allow UDP/TCP/TLS 5060/5061 from Orbit’s edge), or the port forward points to the wrong internal address. Symptom check: if the PBX logs show no arrival, the failure is reachability; if logs show arrival but no 200 OK, the failure is auth or transport. Still stuck. Walk SIP trunk troubleshooting for the symptom-first checklist, and for incoming-side rejections read lastCallVerdictReason on the trunk — auth failure, caller-id mismatch, routing-rule violation, or billing hold — and follow the incoming diagnostics in Connect a SIP trunk end to end.

Production checklist

  • Carrier/PBX SIP details captured and a DID owned on the account
  • Probe run before save, with probe_status carried onto the row
  • Transport chosen (tls preferred) and captured in the row
  • Incoming authMode chosen; allowlist entries are specific IPs, never 0.0.0.0/0
  • Digest password captured at create (or rotated + revealed once)
  • DID inbound route set (PUT /numbers/+E164/routing)
  • routing-analysis simulated on a few representative destinations
  • test returns registered (outgoing) / ok (incoming); onboarding verify returns verified and stamps onboarded_at
  • test-call completed media end to end
  • Softphone credentials show sip_srtp: "Mandatory" in every UA
  • failoverTrunkId set on trunks carrying traffic
  • Health polled into your monitoring, alerting on counts.down > 0 or counts.degraded > 0

See also