> ## Documentation Index
> Fetch the complete documentation index at: https://docs.orbit.devotel.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Troubleshooting: SIP trunk registration, health, and failover

> Diagnose a SIP trunk that cannot register, keeps failing health checks, or will not fail over — decode the SIP response codes, read the trunk-health endpoint, and check TLS and the inbound auth ledger.

# Troubleshooting: SIP trunk registration, health, and failover

A trunk problem shows up one of three ways: the trunk never registers at
all, it registers but keeps failing periodic health checks (flapping), or
it is registered but calls still do not route the way you expect — inbound
calls rejected, or failover never engaging when the primary is down. This
page maps each symptom to a cause and tells you what to check before you
change anything. For the dashboard walkthrough (SIP settings table,
route-quality view), see
[Troubleshooting: SIP trunk down or calls failing](/channels/voice/sip-trunks-troubleshooting);
this page goes deeper on the SIP layer itself.

## Trunk state basics

Each trunk you create carries a set of live diagnostic fields on top of its
configuration — these are what every check on this page reads:

* `status` — the cached registration verdict: `registered`,
  `unregistered`, or `unknown` (no probe data yet).
* `lastRegistrationError` — the last recorded failure, with the SIP
  response code where the carrier returned one (for example `401`, `488`)
  or a step name where it never got that far (`DNS resolution`,
  `TCP/TLS connect`).
* `lastRegistrationCheckAt` — when the verdict was last refreshed.
* `registrationFailureCount` — consecutive failed checks since the last
  success. A non-zero streak on an otherwise `registered` trunk is the
  early-warning signal.
* `registrationFailingSince` — the wall-clock start of the current failure
  streak.

Read the full record for one trunk:

```bash theme={null}
curl "https://api.orbit.devotel.io/api/v1/voice/sip-trunks/trunk_abc123" \
  -H "X-API-Key: $ORBIT_API_KEY"
```

Or read the ranked snapshot of every trunk, with the rows that need
attention first and per-severity tallies:

```bash theme={null}
curl "https://api.orbit.devotel.io/api/v1/voice/sip-trunks/health" \
  -H "X-API-Key: $ORBIT_API_KEY"
```

Two mechanics sit behind these fields:

* **Health evaluation.** The platform re-checks each enabled trunk on a
  cadence of about five minutes, running a real SIP REGISTER with the
  trunk's stored credentials and folding the verdict back onto the trunk
  record. An operator-run test (`POST /api/v1/voice/sip-trunks/:id/test`
  or **Test trunk** in the dashboard) runs the same probe on demand and
  updates the same fields, so a fix you made is provable within seconds
  without waiting for the next poll.
* **Failover routing.** When the dispatch path finds the primary trunk is
  anything other than `registered`, it walks the failover chain you
  configured and picks the first candidate that is enabled, has a dial
  address, and is itself `registered`. A chain with no usable candidate —
  or none configured — falls back to Orbit's default outbound routing, so
  a misconfigured failover worsens redundancy but never blocks calls.
  Chain writes are cycle-checked at save time and hop-bounded at dispatch
  time, so a loop cannot hang dispatch.
* **Inbound auth ledger.** For incoming trunks (your carrier originates
  calls into the platform), every failed auth attempt — digest challenge or
  IP allowlist — is recorded against the trunk for seven days. That ledger
  is the first place to look when inbound calls stop arriving while the
  trunk row looks healthy.

## Symptom → cause

Work the table top-down — the rows cover the overwhelming majority of
trunk incidents.

| Symptom                                                     | Most likely causes                                                                                                                                                                                    | What to do                                                                                                                                                                                                                                                                             |
| ----------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **REGISTER times out** (no SIP response at all)             | DNS does not resolve the trunk host; a firewall or ACL drops UDP 5060 or TCP 5061 outbound from our side, or inbound from your carrier on incoming trunks; wrong transport for the port (TLS on 5060) | Read `lastRegistrationError` — it names the failed step (`DNS resolution` vs `TCP/TLS connect`). Confirm the host/port/transport on the trunk match your carrier's published SBC address, and check your carrier-side ACL allows the platform edge IPs                                 |
| **401 or 403 on REGISTER**                                  | Digest username/password mismatch — credentials rotated on the carrier side, a trailing space pasted at provisioning, or the wrong realm                                                              | Update the trunk's authentication fields and retest. If your carrier re-issued credentials, rotate them into the trunk from its settings — a `401` is the carrier answering, so DNS and firewall are already ruled out                                                                 |
| **488 Not Acceptable Here on INVITE/REGISTER**              | Codec mismatch — the carrier rejected the offered media                                                                                                                                               | Orbit negotiates OPUS, PCMU (G.711 µ-law), and PCMA (G.711 A-law); enable at least one of those on the carrier side. See [the codec question in the FAQ](/reference/faq#what-codecs-does-orbit-support)                                                                                |
| **OPTIONS keepalive failing** (trunk registers, then drops) | A firewall or NAT device between the carrier and the platform expires the pinhole between REGISTER refreshes; the carrier's OPTIONS is answered but its state times out                               | Check NAT binding timeouts on your side of the path — the [NAT glossary entry](/reference/glossary) covers why SIP signaling and media need different handling. If the carrier re-INVITEs or re-REGISTERs arrive from a changing source IP, prefer digest auth over a static allowlist |
| **Inbound calls rejected while the trunk looks healthy**    | The incoming auth ledger shows source IPs not in the trunk's allowlist, or digest attempts failing on a rotated password; the trunk's auth mode no longer matches how the carrier sends               | Read the auth-failures ledger (below). Add every SBC egress IP to the allowlist, or switch the trunk to digest auth if the carrier's egress IPs change                                                                                                                                 |
| **Failover never kicks in when the primary is down**        | The failover target is disabled, itself `unregistered`, or has no dial address — the chain walk skips such candidates; the trunk row alone cannot tell you this                                       | Query the health snapshot and check the failover target's `severity`, not just the primary's. A target must be enabled **and** `registered` for dispatch to pick it                                                                                                                    |

## Reading the trunk-health endpoint

`GET /api/v1/voice/sip-trunks/health` returns one ranked row per trunk
plus a `counts` block. The ranking exists so the trunk that needs
attention sorts first; read the severity ladder per row:

| Severity   | Condition                                              | Meaning                                                                                                                                                   |
| ---------- | ------------------------------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `down`     | `status` is `unregistered`                             | Registration is failing right now — work the symptom table above                                                                                          |
| `degraded` | `status` is `registered` but `consecutiveFailures` > 0 | The trunk flaps — individual probes fail between successes. Intermittent DNS, a carrier-side rate limit, or an expiring NAT binding; watch `failingSince` |
| `active`   | `status` is `registered`, no failure streak            | Healthy                                                                                                                                                   |
| `unknown`  | No probe data yet                                      | A brand-new or disabled trunk that has not been checked                                                                                                   |

Within a severity, rows sort by consecutive-failure streak and then by
staleness of the last check — a trunk that stopped being probed at all is
worse news than one still answering. Use `lastCheckedAt` to tell "the
trunk broke" apart from "the checks stopped": if `lastCheckedAt` is hours
old on an enabled trunk, the verdict is stale and you should force a
fresh probe with the test endpoint rather than trusting the cached
`status`.

The same severity fields explain failover behavior: dispatch only uses a
failover candidate in the `active` state (enabled and `registered`), so a
backup sitting at `degraded` or `down` is skipped exactly as if it were
unconfigured.

## TLS and SRTP

Trunks configured with TLS transport (`port 5061`) fail differently from
UDP/TCP trunks and deserve their own pass:

* **Certificate chain.** The carrier's SIP endpoint must present a
  complete chain to a public root. A missing intermediate opens as a
  `TCP/TLS connect` failure in `lastRegistrationError` even though the
  port is reachable — confirm with `openssl s_client -connect host:5061
  -servername host` from any network and look for an unbroken chain.
* **SAN mismatch.** The certificate's subject-alternative names must
  include the exact host you configured on the trunk. A trunk pointed at
  `sip-carrier.com` while the certificate names `sip1.sip-carrier.com`
  fails the handshake the same way — align the trunk host with the
  certificate SAN (or get the carrier to reissue).
* **SRTP media.** When the trunk negotiates TLS, media keys ride the
  encrypted signaling (SDES). If registration succeeds but calls connect
  with no audio, the carrier side is either declining SRTP or offering a
  keying mode the trunk did not accept — check the carrier's media-security
  setting, not the codecs. The full one-way-audio / dead-air decision path
  lives in
  [Troubleshooting: voice call quality](/troubleshooting/voice-call-quality).

After any TLS change, run the trunk test rather than waiting for the next
poll: `POST /api/v1/voice/sip-trunks/:id/test`.

## What not to do

* **Do not retry blindly.** Re-running registration against an unanswered
  `401` or a timeout changes nothing and can trigger carrier-side rate
  limits that turn a flap into a hard block. Read the recorded error
  first, fix the named cause, then test once.
* **Do not rotate credentials while the trunk is flapping.** A rotation
  mid-flap mixes two failure modes — you can no longer tell whether the
  next `401` is the old credential cached somewhere or the new one wrong.
  Stabilize the trunk first, then rotate as its own step, then test
  again.
* **Do not edit the failover chain while a failover event is in
  progress.** Dispatch reads the chain per call; changing targets
  mid-incident makes already-routed and newly-routed calls disagree about
  which trunk was the backup.

## When to escalate

Open a support ticket when you have worked the symptom table, forced a
fresh probe, and the trunk still fails with an error this page does not
explain. Include all three so we can trace the attempt without a
back-and-forth:

1. The **trunk ID** (`trunk_…`).
2. Your **organization ID** (Settings → Organization, or `organizationId`
   from `GET /api/v1/me`).
3. The **last SIP response code or failed step** from
   `lastRegistrationError` (for example `403`, or `DNS resolution`), plus
   the `lastRegistrationCheckAt` timestamp so we can find the exact
   attempt.

## See also

* [Voice channel: SIP trunks](/channels/voice) — trunk setup and the
  connect-your-carrier overview.
* [Troubleshooting: SIP trunk down or calls failing](/channels/voice/sip-trunks-troubleshooting)
  — the dashboard-side views: settings table, route quality, auth ledger.
* [Troubleshooting: voice call quality](/troubleshooting/voice-call-quality)
  — one-way audio, dead air, jitter, and PDD on calls that do connect.
* [AI Voice Agents via sip\_forward](/guides/ai-voice-agent-sip-forward) —
  forwarding inbound trunk calls to an external voice agent.
* [Voice API reference](/api-reference/voice) — full request/response
  schemas for every endpoint used on this page.
