Your SIP trunk

Place agent calls from a number that you already have at your own carrier. Stream sends these calls through your carrier's SIP trunk. Stream does not sell or hold the number.

A number on your SIP trunk is for outbound calls only. To answer calls, buy a Telnyx or Twilio Stream number.

When to use your SIP trunk

Use your own SIP trunk and not a Stream number when:

  • You need a number in a country that Stream does not sell. See Countries.
  • Your carrier must carry the calls under your own contract.
  • You need a number type that Stream does not sell, or you must keep a number that your customers already know.
  • You plan a large number of outbound calls, for example campaigns.

What you need from your carrier

Get these values from your carrier:

  • The host and the port of the trunk
  • The transport that the trunk accepts
  • A username and a password for digest authentication
  • The codecs that the trunk accepts
  • Whether the trunk accepts an INVITE without SDP
  • A number that your carrier lets you use as caller ID

The table below gives the format and the default of each value. Type the values when you add the trunk.

Values in Stream

The API field is the name in the CLI and API requests.

Dashboard field API field Value
Name name A name that you choose, for example main-carrier.
Host host The hostname only, for example sip.example.com. An IP address must be public.
Port port A number from 1 to 65535. The default is 5060.
Transport transport UDP, TCP or TLS. The default is TCP.
Username username The username for digest authentication.
Password password The password for digest authentication.
Codecs codecs One or more of PCMU, PCMA and G722, in order of preference.
Ring without an SDP offer late_offer On only if the trunk accepts an INVITE without SDP. See Late offer.
Number e164 The caller ID, in E.164 format with + and the country code, for example +15125550142.
Country country The two-letter code of the country of the number, for example US.

Codecs

Stream offers the codecs in the order that you type them. The trunk selects one of them. In the API, if you do not send codecs, Stream uses PCMU, then PCMA. In the dashboard, the Codecs field starts with PCMU, PCMA. Use G722 only if your carrier accepts it.

Late offer

With late offer off, Stream sends its SDP offer in the INVITE. With late offer on, Stream sends the INVITE without SDP, and the trunk sends the offer in its answer. Set late offer on only if your carrier accepts an INVITE without SDP.

In the API, late_offer is false if you do not send it. In the dashboard, Ring without an SDP offer is on for a new trunk. Set it off if your carrier does not accept late offer.

Authentication

Stream sends the INVITE without credentials first. Your trunk must challenge the INVITE with 401 Unauthorized or 407 Proxy Authentication Required. Stream then sends the INVITE again with your username and password.

If your trunk does not challenge the INVITE, BYE and other requests from the trunk do not reach Stream. A trunk that accepts calls by IP address only does not send a challenge.

Caller ID

Stream sends the number in E.164 format, with the +. Stream does not buy the number and does not check it with your carrier. Your carrier decides if you can call from the number. Most carriers accept a number on your account, or a number that they verified for you.

Set up in the dashboard

1

Add the trunk

Go to Agents > Telephony > SIP trunks and click Create SIP trunk. Type the settings of the trunk: Name, Host, Port, Transport, Username, Password, Ring without an SDP offer and Codecs. Then click Save.

The New SIP trunk form
2

Add a number

On the trunk's page, click Add number. Type the Number and the Country, then click Add number.

The Add number dialog

The number appears under Numbers on the trunk's page.

The trunk's numbers, with Test call and Add number above them
3

Make a test call

Click Test call. See Make a test call.

The dashboard does not show the stored password. To keep the password, leave the Password field empty when you click Save.

To release a number, click Remove next to the number, then click Remove in the dialog. The number stays with your carrier, and you can add it again.

To delete the trunk, open the actions menu next to Save and click Delete. Delete is not available while the trunk has numbers.

Twilio

Set up a Twilio Elastic SIP trunk for termination, then enter its values in Stream.

Set up at Twilio

  1. In the Twilio Console, open Elastic SIP Trunking and create a trunk.
  2. On the Termination page of the trunk, type a name for the termination URI, for example example. Twilio makes the URI example.pstn.twilio.com.
  3. Twilio also shows a localized URI for each edge, for example example.pstn.frankfurt.twilio.com. You can use the main URI or the localized URI of any edge.
  4. On the same Termination page, under Authentication, create a credential list with a username and a password. Stream uses these values for digest authentication.
  5. On the General page of the trunk, leave Secure Trunking off. With Secure Trunking on, Twilio refuses calls without encrypted audio. Stream does not encrypt the audio with SRTP.
  6. Open Communications > Voice > Settings > Geo permissions and allow each country that you call.
  7. Make sure that you have a caller ID: a Twilio number on your account, or a number in Verified Caller IDs.

You do not have to put the number on the trunk for outbound calls. If you put a number on the trunk, Twilio sends the inbound calls of that number to the trunk. Then the number does not receive calls in other places, for example on a webhook or in a different app.

Values for Twilio

Add the trunk with these values:

Field Value
Name A name that you choose, for example twilio
Host The termination URI without sip:, for example example.pstn.twilio.com or example.pstn.frankfurt.twilio.com
Port 5060 for TCP. 5061 for TLS.
Transport TCP
Username The username in the credential list. Do not use the friendly name of the list.
Password The password in the credential list
Codecs PCMU, PCMA. Twilio does not accept G722.
Ring without an SDP offer On
Number A Twilio number on your account or a verified caller ID, for example +15125550142
Country The two-letter code of the country of the number, for example US

Then add the number and make a test call, as in Set up in the dashboard. If the phone does not ring, find the response in the call logs of the Twilio Console, and the error code in the Twilio Debugger. Then use the table below.

Twilio errors

Response or error Cause Fix
403 after the 407 challenge The username or the password is wrong. Type the username of the credential list, not its friendly name. Type the password again.
403 No International Authorization, error 32205 in the Twilio Debugger The geo permissions do not allow the country that you call. Allow the country in Communications > Voice > Settings > Geo permissions.
403 Invalid Caller ID The From number is not a Twilio number on your account or a verified caller ID. Use a Twilio number, or add the number to Verified Caller IDs.
400 A number is not in E.164 format. Type the number with + and the country code.
503 Trunk concurrent call limit exceeded or 503 Trunk CPS limit exceeded The account is above its limits. Make fewer calls at the same time.

For other responses, see SIP responses.

Telnyx

Set up a Telnyx Credentials SIP Connection and an Outbound Voice Profile, then enter the values in Stream.

Set up at Telnyx

  1. In the Telnyx Portal, open Real-time Communications > Voice > SIP Trunking.
  2. Create a SIP connection of the type Credentials. Set a username and a password, or let Telnyx make them.
  3. Open Outbound Voice Profiles and click Add New Profile. Select the destinations that you call.
  4. Add the connection to the profile. You can also select the profile on the Outbound tab of the connection.
  5. If your number is not a US number, set the Localization on the Outbound tab of the connection. Without it, Telnyx checks the caller ID as a US number.
  6. Make sure that you have a caller ID: a Telnyx number on your account, or a Verified Number.

Calls outside the US need a Level 2 account. Many other destinations also need Level 2.

Values for Telnyx

Add the trunk with these values:

Field Value
Name A name that you choose, for example the name of the connection
Host sip.telnyx.com. For the EU, use sip.telnyx.eu. Other regions: sip.telnyx.com.au, sip.telnyx.ca, sip.telnyx.me, sip.telnyx.asia (beta)
Port 5060 for UDP and TCP. 5061 for TLS.
Transport TCP. Telnyx also accepts UDP, and TLS 1.2 or higher.
Username The username of the connection
Password The password of the connection
Codecs PCMU, PCMA, G722, in the order that you prefer
Ring without an SDP offer Off or on. Telnyx accepts an INVITE without SDP.
Number A Telnyx number on your account or a Verified Number, for example +15125550142
Country The two-letter code of the country of the number, for example US

Then add the number and make a test call, as in Set up in the dashboard. If the phone does not ring, find the response in the call logs of the Telnyx Portal. Telnyx puts a code in the reason text, for example D38.

Telnyx errors

Response or error Cause Fix
403 with D38, D7 The connection has no Outbound Voice Profile. Add the connection to an Outbound Voice Profile.
403 with D51 The caller ID is not a Telnyx number, and it is not a Verified Number. Use a Telnyx number, or add the number to your Verified Numbers at Telnyx.
403 with D35 The format of the caller ID is wrong. Type the number with + and the country code. Set the Localization for a non-US number.
403 with D1 The account is above its limit of calls at the same time. Make fewer calls at the same time. A Level 2 account can have 10 calls at the same time.

For other responses, see SIP responses.

Limits

Warning:
  • A number on your trunk cannot receive calls. You cannot attach it to a Stream call.
  • A call from a number on your trunk cannot send custom SIP headers. Stream refuses the call.
  • If the host, password or caller ID is wrong, the API does not tell you. See The response does not wait for the call.
  • If the Stream server that holds a call through your trunk restarts, Stream ends the call.

SIP responses

The API does not show the SIP response of your carrier. Find the response in the call logs of your carrier.

These are the usual meanings. Your carrier can use a code in a different way, so also read the reason text.

Response Usual cause Fix
No response The host, port or transport is wrong, or a firewall stops SIP. Check the Host, Port and Transport with your carrier.
400 Bad Request The carrier does not accept the request, often the number format. Make sure that the carrier accepts numbers in E.164 format with +.
401 Unauthorized or 407 Proxy Authentication Required, again after the credentials The username or the password is wrong. Type the username and the password again. Use the username, not another name of the credentials.
403 Forbidden The carrier refuses the call: the credentials, the caller ID or the destination. Check the credentials, then the Number, then the countries that your account can call.
404 Not Found The carrier does not know the number that you call. Check the To number.
408 Request Timeout The carrier did not receive an answer in time. Check the Host and the Transport. Call again.
480 Temporarily Unavailable or 486 Busy Here The person that you call is not available. Call again later.
488 Not Acceptable Here The carrier does not accept the codecs or the SDP. Change the Codecs. Try PCMU, PCMA.
503 Service Unavailable The carrier has a problem, or you are above a call limit. Check the limits of your account: calls at the same time and calls per second.

For the steps to test a call, see Test and troubleshoot.

Set up with the CLI or the API

To place a call from a number on your trunk, see Making calls.

Create a trunk

With the Stream CLI:

curl -fsSL https://getstream.io/cli/beta/install.sh | bash # the agents commands are in the beta CLI
getstream agents api CreateSipTrunk --request '{
  "name": "main-carrier",
  "host": "sip.example.com",
  "port": 5060,
  "transport": "tls",
  "username": "stream-agent",
  "password": "'"$SIP_TRUNK_PASSWORD"'",
  "codecs": ["PCMU", "PCMA"],
  "late_offer": false
}'
use reqwest::Method;
use serde_json::{json, Value};

let client = agent.router()?;

let trunk: Value = client
    .send(
        Method::POST,
        "/v1/phone/trunks",
        None::<&()>,
        Some(&json!({
            "name": "main-carrier",
            "host": "sip.example.com",
            "port": 5060,
            "transport": "tls",
            "username": "stream-agent",
            "password": std::env::var("SIP_TRUNK_PASSWORD")?,
            "codecs": ["PCMU", "PCMA"],
            "late_offer": false,
        })),
        "createSipTrunk",
    )
    .await?;
println!("created {}", trunk["id"]); // "5d2f7e0a-8c1b-4f3e-9a6d-2b7c1e4f8a90"

Do not write the password in your code. The examples read it from the SIP_TRUNK_PASSWORD environment variable. The Rust example needs the reqwest and serde_json crates in your project. If you do not send a field, the trunk uses the default value. See Values in Stream.

Add a number

With the Stream CLI:

getstream agents api AddTrunkNumber --id 5d2f7e0a-8c1b-4f3e-9a6d-2b7c1e4f8a90 --request '{"e164": "+15125550142", "country": "US"}'
let id = trunk["id"].as_str().ok_or("the trunk has no id")?;
let number: Value = client
    .send(
        Method::POST,
        &format!("/v1/phone/trunks/{id}/numbers"),
        None::<&()>,
        Some(&json!({ "e164": "+15125550142", "country": "US" })),
        "addTrunkNumber",
    )
    .await?;
println!("{} {}", number["vendor"], number["sip_trunk_id"]); // "sip_trunk" "5d2f7e0a-8c1b-4f3e-9a6d-2b7c1e4f8a90"

If you already have the number, Stream refuses it. Your carrier decides if you can call from the number. See Caller ID. To list and release numbers on your trunk, see Manage your numbers.

Change a trunk

With the Stream CLI:

getstream agents api UpdateSipTrunk --id 5d2f7e0a-8c1b-4f3e-9a6d-2b7c1e4f8a90 --request '{"transport": "tcp", "codecs": ["PCMA", "PCMU"]}'
// No password in the body: the stored password stays.
let changed: Value = client
    .send(
        Method::PATCH,
        "/v1/phone/trunks/5d2f7e0a-8c1b-4f3e-9a6d-2b7c1e4f8a90",
        None::<&()>,
        Some(&json!({ "transport": "tcp", "codecs": ["PCMA", "PCMU"] })),
        "updateSipTrunk",
    )
    .await?;
println!("{}", changed["has_password"]); // true

Fields that you do not send keep their values. If you do not send password, the trunk keeps the stored password. If has_password is false, set a password before you place calls.