Skip to main content

Troubleshooting: outbound calls rejected with SBC_UNAVAILABLE

SBC_UNAVAILABLE (HTTP 503) is the refusal the voice API returns when it could not hand an outbound call to the session border controller (SBC) that terminates it. Every outbound call leaves Orbit through an SBC, either on your own SIP trunk or on the platform’s wholesale routing, so the cause lives on one of three sides: your trunk’s SBC, the upstream carrier, or a transient platform blip. This page walks the ladder that finds which, and tells you when a retry helps and when only a config change does.

What you see

  • API dispatch. POST /api/v1/voice/calls (and the softphone dial that goes through it) answers 503 with the code in the error envelope:
Copy meta.request_id now; it is the anchor support traces by.
  • Dialer-dispatched outbound legs. Campaign and journey calls do not return the 503 to you directly. The leg lands as failed in the campaign’s call results, and the same row shows in the Voice call log (filter status=failed, direction=outbound, see Voice call logs).
  • Webhook. Each rejected leg also arrives as a call.failed event on your voice webhook endpoint (see Webhook events), so an integration that watches call.failed sees the refusal even when no operator is looking at the dashboard.
On a softphone dial the operator sees a short “try again in a moment” notice rather than the raw code; the envelope above is what your API integration receives.

Causes

Work top-down. The first three are yours or your carrier’s; the last is the platform’s.
  1. Your trunk’s SBC is unreachable. The target SBC is down, or a DNS record or IP allow-list changed on your side (a BYOC firewall rule, a rotated egress allow-list) so the platform edge is refused. This is the most common cause on customer-managed trunks.
  2. Carrier-side outage on the upstream route. The SBC answers but the carrier’s next hop is down; failures arrive in a burst that matches the carrier’s maintenance window rather than any change of yours. Check your carrier’s status channel and the Orbit status page.
  3. TLS or profile mismatch after a trunk edit. A certificate that no longer chains, a subject name that no longer matches the host, or a codec/transport profile the carrier rejects at INVITE time. Rejects that begin exactly when you saved a trunk edit are this until proven otherwise; the TLS pass lives in SIP trunk troubleshooting.
  4. A transient platform media blip. The media plane that bridges your call onto the SBC occasionally loses signaling connectivity for seconds at a time, and the API answers the same clean 503. This class self-heals in moments and is the one retry-with-backoff is for; if it persists beyond a few minutes it is an incident, not a blip.
SBC_UNAVAILABLE means the platform never placed the call on the wire. Refusals from the pre-send checks (emergency, destination, caller identity) return their own 4xx codes instead; see the voice pre-send gate chain for that ladder.

Debug ladder

Run these in order; each step narrows which cause above you are in.
  1. Read the trunk record. GET /api/v1/voice/sip-trunks/:id, or Settings → Voice → SIP trunks in the dashboard, returns status, lastRegistrationError, and lastRegistrationCheckAt. A status other than registered with a named lastRegistrationError (DNS resolution, TCP/TLS connect, 401, 488) puts the fault on your trunk’s path; fix the named cause per the SIP trunk runbook.
  1. Compare timestamps. Line the first call.failed event up against the trunk’s last config change and its lastRegistrationCheckAt. Failures that begin the minute a trunk edit saved point at cause 3; failures that begin with no change of yours point at cause 1 or 2.
  2. Reproduce with one manual call. Place a single call by hand, from the softphone or one POST /api/v1/voice/calls. If the lone call fails exactly like the dialer’s burst, the fault is per-call reachability or config, not dialer concurrency. If only the dialer fails while manual calls pass, you are on a capacity gate instead, and the capacity section of the SIP trunk runbook is the page you want.
  3. Prove the path with an inbound test. If the trunk also carries inbound calls, place a test call into it. Inbound succeeding while outbound fails narrows the fault to the outbound route or profile; both directions failing puts it on reachability (cause 1).

Fix vs retry

The code alone does not tell the two apart; the failure pattern does. A deterministic config fault never clears on retry, and retrying it burns dialer attempts against a dead setting. A transient outage clears on its own and rewards spaced retries. Two rules keep this safe:
  • Never retry a deterministic fault as-is. An unchanged re-issue of a refused call returns an unchanged 503, and a tight loop can trip carrier-side rate limits that turn a misconfiguration into a block. Edit, re-test the trunk, then re-issue once. For API-originated calls, send an Idempotency-Key on the re-issue so a retry racing the recovery cannot originate the call twice (see Idempotency and billing gates).
  • Back off the transient class, do not flat-retry it. Spaced attempts ride a blip out; a tight loop turns the blip into a backlog.

Bring Your Own Carrier trunks

If the trunk is customer-managed (your SBC, your carrier contract), the platform fails closed: a trunk it cannot reach is refused with this code, except where your configured failover chain or the dispatch fallback described in the SIP trunk runbook routes around it. Everything on your side of the path (the SBC, its DNS, its firewall allow-lists, its certificates) is yours to fix; work the ladder above and re-test. Note the split with caller identity: an INVITE whose caller-id is outside the trunk’s allowedCallerIds list is a 403 VOICE_CALLER_ID_REJECTED, not this 503 (see the FAQ entry). If the trunk is platform-managed, the same 503 pages Devotel on-call automatically; there is nothing to edit on your side and no retry budget to wait out before opening a ticket. Either way, include the bundle below so the first response is not a request for more data.

The bundle support needs

  • The request_id from meta.request_id on the 503 envelope, or from the failed row in Insights → Logs for dialer-dispatched legs.
  • The trunk_id (trunk_…) the call was placed through, or “platform default” when you do not run your own trunk.
  • The window: first and last call.failed timestamp, and whether any trunk or carrier change preceded it.

What not to do

  • Do not loop the retry on a config fault. An unchanged re-issue changes nothing; back off, fix, re-issue once.
  • Do not edit the trunk while a burst is in flight. Changing host or credentials mid-burst mixes two failure populations and makes the timestamp comparison in the ladder meaningless.
  • Do not rotate carrier credentials to chase a blip. A rotation only answers a 401-class lastRegistrationError; on an unreachable SBC it adds a second variable.

When to escalate

Open a support ticket when you have worked the ladder, re-tested the trunk, and the 503 persists past your backoff window, or whenever a platform-managed trunk repeats the refusal. Attach the bundle above plus your organization ID (Settings → Organization). If the trunk is customer-managed and the ladder points past your edge, take the lastRegistrationError text and the timestamps to your carrier; the platform records what it saw on its side of the handshake, and that is the evidence your carrier’s SBC team needs.

See also