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

# SIP trunks

> Bringing your own carrier.

Wixzel Phone does not sell phone numbers or minutes. You connect a SIP trunk
from a carrier you choose, and your carrier bills you directly at your rates.

That is a deliberate trade. It means one more step before your first call, and
it means we are never between you and your telephony provider — no markup, no
resale margin, and no reason for us to care which carrier you pick.

## The IP to allowlist

<Info>
  Calls are placed from **`95.216.218.102`**.

  Nearly every carrier authorises outbound calls by source IP, so this address has
  to be on your trunk's allowlist before anything will connect. Do this first: a
  trunk that is correct in every other respect still fails without it, and the
  failure looks like a fault at our end rather than a missing permission.

  Every trunk returns it as `platform_ip`, so you can read it from the API rather
  than trusting this page to stay current:

  ```bash theme={null}
  curl https://api.phone.wixzel.com/v1/sip-trunks \
    -H "Authorization: Bearer $WIXZEL_API_KEY" | jq -r '.data[0].platform_ip'
  ```
</Info>

## Where inbound calls should be sent

<Info>
  Send inbound calls to **`sip:95.216.218.102:5090`**.

  This is the other half of a trunk, and it is separate configuration in a
  different part of your carrier's console from the allowlist above. The allowlist
  tells them to accept calls **from** us; this tells them where to deliver calls
  **to** us. Twilio calls the field the **Origination URI**; other carriers call it
  a termination point, a destination SIP URI, or just the host. It is the same
  string.

  Getting this wrong fails in the quietest way this product has: you buy a number,
  point it at an agent, and it simply never rings. Nothing appears in your logs or
  ours, because the call never arrives — the carrier had nowhere to deliver it.

  Every trunk returns it as `origination_uri`, so read it from the API rather than
  trusting this page to stay current:

  ```bash theme={null}
  curl https://api.phone.wixzel.com/v1/sip-trunks \
    -H "Authorization: Bearer $WIXZEL_API_KEY" | jq -r '.data[0].origination_uri'
  ```
</Info>

## What you need

Any SIP provider will do. From them you need:

* a **host** — `sip.carrier.example`
* **credentials** — username and password, if they use registration
* your numbers, and whether inbound calls are delivered to that trunk
* an **IP allowlist entry** for `95.216.218.102`, or a credential list if you
  would rather authenticate with a username and password

## Connecting it

```bash theme={null}
curl https://api.phone.wixzel.com/v1/sip-trunks \
  -H "Authorization: Bearer $WIXZEL_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "My carrier",
    "host": "sip.carrier.example",
    "port": 5060,
    "transport": "udp",
    "username": "acct123",
    "password": "…"
  }'
```

`transport` is `udp`, `tcp` or `tls`.

<Note>
  The password is encrypted at rest and is never returned by the API again —
  including to you. Store it wherever you keep the rest of your carrier
  credentials.
</Note>

## Registering a number

A number must belong to a trunk before it can place a call:

```bash theme={null}
curl https://api.phone.wixzel.com/v1/phone-numbers \
  -H "Authorization: Bearer $WIXZEL_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "phone_number": "+14155550100",
    "sip_trunk_id": "st_01HXYZ"
  }'
```

Give it an `inbound_agent_id` and calls **to** that number are answered
automatically by that agent.

## Carrier-side setup

Your carrier needs to know where to send traffic, and — more often the cause of a
silent failure — needs to agree that you are allowed to place the call at all.
Work through all of these before blaming the API:

<Steps>
  <Step title="Allowlist the Wixzel Phone IP">
    Wixzel Phone authenticates to your carrier by IP unless you gave the trunk a
    `username` and `password`. If the IP is not on your carrier's allowlist,
    your `INVITE` is rejected before it is ever rated, and nothing appears on
    your bill to hint at why.

    Calls originate from **`95.216.218.102`**. Add that to whatever your carrier
    calls its IP access control list, and see the carrier notes below for where
    that setting lives on the common providers.

    <Warning>
      This is the single most common reason a trunk that works elsewhere does not
      work here. A trunk configuration that is correct in every other respect —
      same host, same port, same credentials — still fails if it was allowlisted
      for a different server. Moving between deployments means updating the
      allowlist at every carrier.
    </Warning>
  </Step>

  <Step title="Verify the caller ID">
    The number in `from_number_id` has to be one your carrier has authorised you
    to present. Carriers routinely accept the call, log it, then drop it when the
    presented caller ID is not provisioned on the account.
  </Step>

  <Step title="Complete KYC and any destination registration">
    Some markets gate outbound calling behind identity checks. In India in
    particular, an account that has not finished KYC — and, for many
    providers, DLT registration — can hold a positive balance and still have
    every outbound call cleared immediately. The portal shows the calls; it does
    not always show the reason.
  </Step>

  <Step title="Check the balance and the destination is enabled">
    A prepaid balance is necessary but not sufficient. Confirm the specific
    destination range is enabled on your account, not just that there is credit.
  </Step>
</Steps>

### Twilio

Twilio authorises outbound calls by source IP, and rejects anything else
instantly.

<Steps>
  <Step title="Open the trunk's termination settings">
    **Elastic SIP Trunking → your trunk → Termination → Authentication.**
  </Step>

  <Step title="Add the platform IP to an IP Access Control List">
    Create or edit an IP ACL and add **`95.216.218.102`**. A Credential List
    works instead, if you would rather authenticate with a username and
    password — put those on the trunk's `username` and `password` fields and
    Wixzel Phone will use them in place of IP auth.
  </Step>

  <Step title="Check the Termination SIP URI matches the trunk host">
    The `host` on your Wixzel trunk must be the same
    `your-domain.pstn.twilio.com` shown in the console.
  </Step>

  <Step title="Confirm the caller ID is a number on that account">
    Twilio rejects a `from_number_id` it does not own.
  </Step>

  <Step title="Set the Origination URI, for inbound">
    **Elastic SIP Trunking → your trunk → Origination**, then add an Origination
    SIP URI of `sip:95.216.218.102:5090` — or whatever
    `GET /v1/sip-trunks` reports as `origination_uri`.

    Outbound works without this; inbound does not, and does not complain. Attach
    your numbers to the trunk in the same console, then give the number an
    `inbound_agent_id` here so there is an agent to answer them.
  </Step>
</Steps>

A missing IP ACL entry shows up as an **immediate rejection**, roughly a second
after the call is placed, with hangup cause 21 (Call Rejected). It does not ring
and it does not time out.

### Wholesale carriers

Most non-Twilio carriers also authorise by IP, and additionally care about number
formatting and account verification. If calls are accepted and then dropped
rather than rejected outright, work through the troubleshooting below —
particularly `send_plus` and the KYC step above.

<Warning>
  An open SIP port is scanned for toll fraud within hours of appearing. Only
  endpoints matching a trunk you have configured are accepted — there is no
  anonymous endpoint — but keep your carrier's own fraud controls on regardless.
</Warning>

Trunk changes take effect immediately: creating, updating or deleting a trunk
rewrites the telephony configuration and reloads it, so there is nothing to
restart.

## When a call does not connect

A call that is accepted by the API and then fails looks like this: `POST
/v1/calls` returns `202` with `status: "queued"`, and a later `GET /v1/calls`
shows `status: "failed"`, a short `duration_seconds`, and `cost_micros: null`.

`cost_micros: null` is the important part. **You are not charged for a call that
never connected** — no answer means no billable seconds, and the credit reserved
at admission is released. A failed call costs you nothing, which also means the
absence of a charge is not evidence of a problem on our side.

Work down this list in order.

<AccordionGroup>
  <Accordion title="1. Read your carrier's own call logs first">
    This is the single most informative step, and it is easy to skip. Your
    carrier's portal will show whether the call reached them at all, what caller
    ID they saw, what destination they parsed, and how they cleared it.

    Three outcomes, and each points somewhere different:

    * **The call is not listed.** It never reached your carrier. The IP
      allowlist, the trunk host or the firewall is wrong.
    * **Listed, answered, with a duration and a cost.** The call worked at the
      telephony layer. If the caller heard nothing, the problem is media, not
      signalling — see the audio note below.
    * **Listed, zero answered, zero cost, a short duration.** The carrier
      accepted your `INVITE` and then cleared the call. This is a carrier-side
      permission problem far more often than a configuration one: caller ID not
      provisioned, KYC incomplete, or the destination not enabled. Nothing on the
      Wixzel Phone side can fix it, and the fastest route is your carrier's
      support with the timestamp and the call id from their log.
  </Accordion>

  <Accordion title="2. Check the number is actually on a trunk">
    `400 number_not_on_trunk` means the number has no `sip_trunk_id`. Calls are
    placed over your trunk, so a number that is not on one has nowhere to go.
  </Accordion>

  <Accordion title="3. 408 Request Timeout: try send_plus: false">
    If your carrier answers `100 Trying` and then `408 Request Timeout` a few
    seconds later, they could not route the destination. The most common reason
    is the leading `+`.

    Wixzel Phone dials `sip:+15551234567@your-carrier` by default, which is what
    Twilio and Telnyx expect. Many wholesale carriers cannot parse a `+` in the
    request URI: they accept the INVITE, fail to route it, and time out. The call
    shows in their portal as received and never answered, which looks like a
    fault at your end.

    Send bare digits instead:

    ```bash theme={null}
    curl -X PATCH https://api.phone.wixzel.com/v1/sip-trunks/$TRUNK_ID \
      -H "Authorization: Bearer $WIXZEL_API_KEY" \
      -H 'Content-Type: application/json' \
      -d '{"send_plus": false}'
    ```

    Setting a `dial_prefix` implies this: a tech prefix is matched literally, so
    the `+` is always dropped when one is configured.
  </Accordion>

  <Accordion title="4. 403 Forbidden: check the caller ID">
    A `403` is a permission decision, usually about who you claim to be rather
    than who you are dialling. Check that the number behind `from_number_id` is
    one your carrier has authorised you to present.

    Carriers commonly reject a call with no caller ID at all. If you are testing
    from a softphone or an Asterisk CLI rather than through the API, confirm you
    are actually sending one — an INVITE with `From: "Anonymous"` earns a `403`
    from most carriers regardless of how well the account is configured.
  </Accordion>

  <Accordion title="5. Rejected in about a second: the IP is not allowlisted">
    A call that fails in roughly a second, with hangup cause 21 (Call Rejected)
    and no ringing, was refused by the carrier before it was routed. On Twilio
    this is almost always the Termination IP Access Control List: add
    `95.216.218.102` to it.

    The distinguishing feature is the timing. An unauthorised source is rejected
    immediately, because the carrier does not need to look anything up to decide.
    A call that is accepted and *then* fails several seconds later is a different
    problem — routing, formatting or account permissions — and the entries above
    cover those.

    This is worth checking first whenever the same trunk works from another
    server. Identical trunk settings do not carry the allowlist with them.
  </Accordion>

  <Accordion title="6. Interpret the hangup cause">
    `Normal Clearing` (Q.850 cause 16) on a call that was never answered is not
    as reassuring as it sounds. It means the far end ended the call deliberately
    rather than erroring — a rejection can be dressed as a normal hangup. Read it
    as "something upstream chose to end this", not as "the call completed".

    A duration of a few seconds with no answer is usually the carrier clearing
    the call, not a person declining it. A human declining a call typically takes
    longer than the ring itself.
  </Accordion>

  <Accordion title="7. An inbound number never rings at all">
    Not a failure — an absence. If the Origination URI is missing from your
    carrier's console the call never leaves them, so there is nothing in our
    logs, no hangup cause, and no failed call to inspect. Outbound working is no
    evidence either way: the two directions are configured separately.

    Check the carrier has `sip:95.216.218.102:5090` (or whatever
    `GET /v1/sip-trunks` reports as `origination_uri`) as the destination for
    that number, that the number is attached to the trunk on their side, and
    that it has an `inbound_agent_id` here so something answers it.
  </Accordion>

  <Accordion title="8. Connected, but no audio">
    If calls connect but there is no audio, the usual cause is RTP not reaching
    the server — check that your firewall allows the media port range, not just
    the signalling port. Signalling on 5060 and media on the RTP range are
    separate holes in the firewall, and opening only the first produces exactly
    this symptom.
  </Accordion>
</AccordionGroup>
