← All insights

Ribbon SBC in Azure for Teams Direct Routing and Twilio: The Steps the Guides Leave Out

The Ribbon and Twilio interop guide was written for AWS and leans on a wizard. This is every step we needed in Azure, with the values we used and the traps we hit.

We needed a Session Border Controller between Microsoft Teams Phone and Twilio Elastic SIP Trunking. We chose a Ribbon SBC SWe Edge (the "SWe Lite" image) running in Azure. It's certified for Teams Direct Routing, it's cheap to run, and Ribbon and Twilio publish an interop guide for exactly this combination.

The interop guide got us started but not finished. It was written for AWS, it leans on the Easy Config wizard, and it skips most of the things that actually decide whether the box connects in Azure. What finally got us to a connected SBC was comparing, setting by setting, against a working Ribbon config from 2023.

This article is that comparison, written down. It covers every step we needed, in order, with the values we used and the traps we hit.

Status, to be upfront: following these steps, both signaling groups come up green, OPTIONS pings flow both ways, Teams admin center shows the SBC as connected, and Twilio calls reach the SBC over TLS. We have not yet confirmed a full end to end call on this build. The SBC answers inbound Twilio calls with an instant 503 before it sends anything to Teams. Our lead suspect is licensing (see Step 12). We'll update this once it's resolved.

All names, IPs and IDs in this article are placeholders. The screenshots come from a live SBC, with our real values masked out.

Contents

Architecture

  Teams (sip.pstnhub...)            Twilio (your-trunk.pstn.twilio.com)
         |  TLS 5061                    |  in: TLS 5062   out: TLS 5061
         |  SRTP                        |  SRTP
         v                              v
  +--------------------------------------------------+
  |  Ethernet 1  10.8.3.6   <->  203.0.113.37         |  sbc.example.com
  |      (all SIP and media, default route here)      |
  |                                                   |
  |  Admin IP    10.8.2.6   <->  203.0.113.58         |  sbc-admin.example.com
  |      (web UI and REST only, never SIP)            |
  +--------------------------------------------------+
         ^
         |  HTTPS 443 from 198.51.100.61 only
      your admin PC

Key ideas:

  • Two NICs. Management on one, calls on the other. This is not optional on Ribbon (Step 1 explains why).
  • Two listening ports. Teams connects on 5061 with mutual TLS. Twilio connects on 5062 with plain server-side TLS. They can't share a port (Step 8).
  • SRTP on both legs, so audio is encrypted end to end. The SBC terminates and re-encrypts media in the middle.

Prerequisites

  • An Azure subscription with rights to create VMs, NICs, public IPs, NSGs and (optionally) DNS records.
  • A verified custom domain in your Microsoft 365 tenant (for example example.com). The SBC FQDN must live under it. *.onmicrosoft.com does not work.
  • Teams Phone licenses for the users who will make and take calls, and Teams admin rights (Teams admin center plus the MicrosoftTeams PowerShell module).
  • A Twilio account with an Elastic SIP Trunk, Secure Trunking turned on, and a credential list.
  • A Ribbon SBC license for the number of calls you need (the built-in trial works for testing; see Step 12).
  • A way to get a public certificate. We used Let's Encrypt with certbot and Azure DNS.
  • A fixed public IP for your own admin PC, so you can lock the web UI down to it.

---

Step 1: Build the VM in Azure (two NICs)

1.1 Pick the image and accept the terms

Ribbon publishes the SBC in the Azure Marketplace. The image URN we used:

az vm image list --publisher ribboncommunications --offer ribbon_sbc_swe-lite_vm --all -o table

# publisher:offer:sku:version
# ribboncommunications:ribbon_sbc_swe-lite_vm:ribbon_sbc_swe-lite_vm_release:13.01.00043

az vm image terms accept \
  --publisher ribboncommunications \
  --offer ribbon_sbc_swe-lite_vm \
  --plan ribbon_sbc_swe-lite_vm_release

1.2 Pick a VM size

What we learnedWhy it matters
The image is Generation 1Newer VM series (the v6 sizes) are Gen2 only and won't boot it.
Standard_B2s runs about 10 sessions fineBurstable and cheap. F2s_v2 is the steady CPU fallback if you see CPU credit exhaustion.
Some sizes had no capacity in our region, and some families had zero quotaCheck az vm list-skus -l <region> --size <size> and your quota before you build.

1.3 Networking: two subnets, two NICs, two public IPs

Gotcha: one NIC does not work. Ribbon never carries SIP or media on the management interface (mgt0, shown as "Admin IP"). Signaling groups bind to "Ethernet 1". On a single-NIC VM, "Ethernet 1" doesn't exist, so the SBC silently binds both signaling groups to mgt0. They show as configured, the TLS listeners even answer, but no calls work: Teams says "The trunk never connected" and Twilio gets 32011. The only event is an easy to miss "Successfully Bound To Network Interface ... interface:mgt0, this SG needs to be bounced manually".

Layout we used:

ItemMgmt sideCall side (WAN)
Subnet10.8.2.0/2410.8.3.0/24
NICprimary NIC, becomes Admin IP (mgt0)second NIC, becomes Ethernet 1 (pkt0)
Private IP10.8.2.610.8.3.6
Public IP203.0.113.58 (static)203.0.113.37 (static)
DNS namesbc-admin.example.comsbc.example.com
NSG443 from your admin IP onlySIP and media rules (Step 5)

Create it roughly like this (placeholders in angle brackets):

RG=<resource-group>; LOC=<region>; VNET=<vnet>

az network vnet create -g $RG -n $VNET -l $LOC --address-prefixes 10.8.0.0/16 \
  --subnet-name sbc-mgmt --subnet-prefixes 10.8.2.0/24
az network vnet subnet create -g $RG --vnet-name $VNET -n sbc-wan --address-prefixes 10.8.3.0/24

az network public-ip create -g $RG -n sbc-admin-pip --sku Standard --allocation-method Static
az network public-ip create -g $RG -n sbc-call-pip  --sku Standard --allocation-method Static

az network nic create -g $RG -n sbc-mgmt-nic --vnet-name $VNET --subnet sbc-mgmt \
  --private-ip-address 10.8.2.6 --public-ip-address sbc-admin-pip --network-security-group sbc-mgmt-nsg
az network nic create -g $RG -n sbc-wan-nic --vnet-name $VNET --subnet sbc-wan \
  --private-ip-address 10.8.3.6 --public-ip-address sbc-call-pip --network-security-group sbc-wan-nsg

az vm create -g $RG -n sbc-01 --size Standard_B2s \
  --image ribboncommunications:ribbon_sbc_swe-lite_vm:ribbon_sbc_swe-lite_vm_release:13.01.00043 \
  --plan-publisher ribboncommunications --plan-product ribbon_sbc_swe-lite_vm \
  --plan-name ribbon_sbc_swe-lite_vm_release \
  --nics sbc-mgmt-nic sbc-wan-nic \
  --admin-username sbcadmin --admin-password '<from your vault>'

If you already built a single-NIC VM (we did), add the second NIC while the VM is deallocated:

az vm deallocate -g $RG -n sbc-01
az vm nic set -g $RG --vm-name sbc-01 --nics sbc-mgmt-nic sbc-wan-nic --primary-nic sbc-mgmt-nic
az vm start -g $RG -n sbc-01

Gotcha: az vm nic add crashed for us with KeyError: 'primary'. az vm nic set with the full list and --primary-nic works.

Gotcha: if you move the call public IP from the old NIC to the new one, the web UI moves too. Use the admin public IP from then on.

After boot, both interfaces show up under Settings > Networking Interfaces > Logical Interfaces, both on DHCP from Azure:

Admin IP (mgt0) on the management subnet
Admin IP (mgt0) on the management subnet
Ethernet 1 on the WAN subnet. It has no next hop field you can use while on DHCP; Step 6 fixes that with a static route.
Ethernet 1 on the WAN subnet. It has no next hop field you can use while on DHCP; Step 6 fixes that with a static route.

Step 2: DNS

Create two A records:

NamePoints toUsed by
sbc.example.com203.0.113.37 (call IP)Teams, Twilio, the certificate
sbc-admin.example.com203.0.113.58 (admin IP)you, for the web UI (optional but handy, and it lets the same cert cover the UI)
az network dns record-set a add-record -g <dns-rg> -z example.com -n sbc       -a 203.0.113.37
az network dns record-set a add-record -g <dns-rg> -z example.com -n sbc-admin -a 203.0.113.58

Why: Teams identifies the SBC by FQDN, checks it against your verified tenant domains, and checks it against the certificate. The FQDN must resolve to the IP the SBC actually sends from (the call IP).

On the SBC, set the host name and domain so the node FQDN matches: Settings > System > Node-Level Settings, Host Name sbc, Domain Name example.com. Set DNS here too (Step 6).

Node-Level Settings: host name, domain, Azure DNS 168.63.129.16, NTP
Node-Level Settings: host name, domain, Azure DNS 168.63.129.16, NTP

Step 3: Get a public certificate

Teams needs a certificate from a CA in the Microsoft Trusted Root Program, with the SBC FQDN in the CN or SAN and the Server Authentication EKU. Let's Encrypt qualifies. We put both names in one cert so the web UI is covered too.

3.1 Issue with certbot and Azure DNS (DNS-01)

DNS-01 means you don't need to open port 80 on the SBC. Two small hook scripts create and remove the challenge TXT record.

auth-hook.sh:

#!/bin/bash
# Called by certbot once per name. CERTBOT_DOMAIN is the name being validated.
ZONE=example.com
RG=<dns-resource-group>
NAME="_acme-challenge.${CERTBOT_DOMAIN%.$ZONE}"
az network dns record-set txt add-record -g "$RG" -z "$ZONE" -n "$NAME" \
  --value="$CERTBOT_VALIDATION" -o none
sleep 30   # give Azure DNS time to publish

cleanup-hook.sh:

#!/bin/bash
ZONE=example.com
RG=<dns-resource-group>
NAME="_acme-challenge.${CERTBOT_DOMAIN%.$ZONE}"
az network dns record-set txt delete -g "$RG" -z "$ZONE" -n "$NAME" --yes -o none

Gotcha: validation tokens can start with a -. Written as --value "$CERTBOT_VALIDATION", the Azure CLI reads the token as a new flag and fails. Use --value="$CERTBOT_VALIDATION" (with the equals sign).

Gotcha: with two names on one cert, the hook runs twice. Derive the record name from CERTBOT_DOMAIN (as above), don't hard-code _acme-challenge.sbc.

Run certbot:

certbot certonly --manual --preferred-challenges dns \
  --manual-auth-hook ./auth-hook.sh --manual-cleanup-hook ./cleanup-hook.sh \
  --preferred-chain "ISRG Root X1" \
  -d sbc.example.com -d sbc-admin.example.com \
  --config-dir ./le --work-dir ./le-work --logs-dir ./le-logs

Why --preferred-chain "ISRG Root X1": it pins the chain to the well-known ISRG Root X1, which Microsoft trusts, instead of an alternate chain.

3.2 Import it into the SBC

Web UI: Settings > Security > SBC Certificates > SBC Primary Certificate > Import, choose PKCS12, upload the .p12 and enter its password. (You can also generate a CSR on the SBC under Generate SBC Edge CSR and import the signed cert; Microsoft recommends that route, but it doesn't fit certbot.)

Build the PKCS12 with modern encryption. The SBC rejects the legacy RC2/3DES format with error 15039 ("insecure and legacy"):

openssl pkcs12 -export \
  -in cert.pem -inkey privkey.pem -certfile chain.pem \
  -keypbe AES-256-CBC -certpbe AES-256-CBC -macalg sha256 \
  -out sbc.p12 -passout file:p12pass.txt

REST API (handy for automated renewals): log in, then post the base64 PKCS12 as form fields. Multipart upload does not work (error 15026).

SBC=https://sbc-admin.example.com
curl -sk -c cj -X POST $SBC/rest/login \
  --data-urlencode "Username=<rest-user>" --data-urlencode "Password=<from your vault>"

curl -sk -b cj -X POST "$SBC/rest/certificate/1?action=import12" \
  --data-urlencode "CertFileName=sbc.p12" \
  --data-urlencode "EncryptedPassword=$(cat p12pass.txt)" \
  --data-urlencode "CertFileOperation=1" \
  --data-urlencode "CertFileContent=$(base64 -w0 sbc.p12)"

curl -sk -b cj -X POST $SBC/rest/logout
shred -u sbc.p12 p12pass.txt

The web server restarts for a few seconds after an import.

Gotcha: GET /rest/certificate/1 returns the private key in CertFileContent. Never log or paste that response.

SBC Primary Certificate: both names as SANs, Let's Encrypt issuer, Verify Status OK
SBC Primary Certificate: both names as SANs, Let's Encrypt issuer, Verify Status OK

3.3 Renewal

Let's Encrypt certs last 90 days. Put the renewal on a calendar or script it: renew with certbot, rebuild the .p12, re-import. If Let's Encrypt changes its intermediate, import the new intermediate as a trusted CA too (Step 4), so the SBC sends the full chain.

Check the result from outside:

openssl s_client -connect sbc.example.com:5061 -servername sbc.example.com </dev/null | grep -E "s:|i:|Verify return"
# expect: leaf -> Let's Encrypt intermediate -> ISRG Root X1, "Verify return code: 0 (ok)"

Step 4: Trusted CAs on the SBC

The SBC checks the certificates Teams and Twilio present. It can only do that if it trusts their roots. Import these under Settings > Security > SBC Certificates > Trusted CA Certificates:

RootWhy
DigiCert Global Root G2Twilio *.pstn.twilio.com chains to it today; Teams is moving to it
DigiCert Global Root CATeams SIP proxies chain to it today
Microsoft RSA Root Certificate Authority 2017Microsoft root for Teams
Microsoft ECC Root Certificate Authority 2017Microsoft root for Teams
Microsoft TLS RSA Root G2Microsoft's next generation TLS root
Microsoft TLS ECC Root G2Microsoft's next generation TLS root
ISRG Root X1, plus the Let's Encrypt intermediatesYour own chain, so the SBC can present it complete
Microsoft Root Certificate Authority 2011Not needed any more, but harmless; our 2023 box had it

Skip Baltimore CyberTrust Root: it expired in May 2025 and Microsoft moved off it. Extra roots do no harm.

Trusted CA table
Trusted CA table

Don't take this list on faith. Roots change. Check what each side presents right now:

openssl s_client -connect sip.pstnhub.microsoft.com:5061 -servername sip.pstnhub.microsoft.com -showcerts </dev/null 2>/dev/null | grep -E "s:|i:"
openssl s_client -connect your-trunk.pstn.twilio.com:5061 -servername your-trunk.pstn.twilio.com -showcerts </dev/null 2>/dev/null | grep -E "s:|i:"

When we checked (September 2026): Teams chained to DigiCert Global Root CA through DigiCert SHA2 Secure Server CA, and Twilio chained to DigiCert Global Root G2 through DigiCert Global G2 TLS RSA SHA256 2020 CA1. Import the last i: line's root if it's missing.

Step 5: Azure NSG rules

On the WAN NIC NSG (call side):

RuleProtocolSourceDestination portWhy
Teams signalingTCP52.112.0.0/14, 52.120.0.0/145061Teams SIP proxies connect in with TLS
Teams mediaUDP52.112.0.0/14, 52.120.0.0/14your SBC media range (ours 16384 to 21383)Teams media processors
Twilio signalingTCPall Twilio signaling ranges (below)5062Twilio origination over TLS to our Twilio port
Twilio mediaUDP168.86.128.0/18your SBC media rangeTwilio media

On the management NIC NSG:

RuleProtocolSourceDestination port
Web UI and RESTTCP198.51.100.61/32 (your admin IP)443

Nothing else inbound on either NIC. Outbound can stay default.

Twilio signaling ranges we allow (from Twilio's IP list, all edges):

54.172.60.0/23   34.203.250.0/23  54.244.51.0/24   54.171.127.192/26
52.215.127.0/24  35.156.191.128/25 3.122.181.0/24  54.65.63.192/26
3.112.80.0/24    54.169.127.128/26 3.1.77.0/24     54.252.254.64/26
3.104.90.0/24    177.71.206.192/26 18.228.249.0/24

Gotcha: allow every Twilio edge, not just the one near you. When one edge has trouble, Twilio retries from another. If that edge's range isn't allowed, the retry fails. With AudioCodes Live Hub earlier we saw this as "Src ip does not match proxyset" on retries. Check Twilio's IP address page for the current list.

Gotcha: use both Microsoft blocks. Microsoft documents 52.112.0.0/14 and 52.120.0.0/14 for signaling and media. Older guides list only 52.112.0.0/14, or 52.122.0.0/15 for the second block. Allow both /14s.

Gotcha: the media range must match. Whatever port range you open for UDP must cover the SBC's media range in Settings > Media > Media System Configuration. Microsoft suggests at least two ports per concurrent call.

Media System Configuration: start port and number of port pairs
Media System Configuration: start port and number of port pairs

Step 6: SBC networking (routes, DNS, NAT)

This is where the Azure layout bites. The SBC now has two interfaces, both on DHCP, and it needs to know which one to use for what.

6.1 Static routes

#DestinationMaskGatewayWhy
1198.51.100.61 (your admin IP)255.255.255.25510.8.2.1 (mgmt subnet gateway)Keeps replies to your browser on mgt0, so the UI keeps working
20.0.0.00.0.0.010.8.3.1 (WAN subnet gateway)Sends all SIP, media and DNS out through Ethernet 1

Add them under Settings > Protocols > IP > Static Routes. Azure always uses the .1 address of a subnet as its gateway.

Static routes
Static routes

Why not just set the next hop on Ethernet 1? On a DHCP interface the SBC won't let you: setting Media Next Hop IP fails with error 14026. The static default route does the same job.

Why it matters: without route 2, about a third of our OPTIONS pings failed and servers flapped between "Not Responding" and "Became Responsive", because some traffic left through mgt0 with the wrong source address. With it, zero failures.

Check the effective table under Settings > Protocols > IP > Routing Table. The default route must point at Ethernet 1:

Effective routing table
Effective routing table

Gotcha: reboot once and check the routing table again. Make sure the default route via Ethernet 1 survives, and that you can still reach the UI.

6.2 DNS

Set the primary DNS server to 168.63.129.16 (Azure's resolver) in Node-Level Settings (screenshot in Step 2). A public resolver as secondary is fine.

6.3 Static NAT on each signaling group

Azure does 1:1 NAT between the public and private IP, so the SBC has to write its public IP into SIP headers and SDP. On each signaling group (Step 7 and 8), under SIP IP Details:

FieldValue
Signaling/Media Private IPEthernet 1 IP
Outbound NAT TraversalStatic NAT
NAT Public IP (Signaling/Media)203.0.113.37
ICE SupportDisabled (no media bypass)

Our 2023 config also set the private media source IP to Ethernet 1; we kept that on both signaling groups (the REST field is PrivateMediaSourceIp).

Gotcha: bounce the signaling groups. After changing a signaling group's interface, the SBC keeps the old binding until the group is disabled and enabled again. In the UI: set Admin State to Disabled, Apply, then Enabled, Apply. Over REST: customAdminState=0, then 1.

Step 7: The Teams side of the SBC

7.1 TLS profile (Teams)

Settings > Security > TLS Profiles, new profile:

FieldValueWhy
TLS ProtocolTLS 1.2 to 1.3Teams requires TLS 1.2 or later
Mutual AuthenticationEnabledTeams presents a client cert; verify it
CertificateSBC Edge CertificateYour Let's Encrypt cert
Handshake Inactivity Timeout10Same as our working 2023 config
Validate Server FQDNEnabledWhen the SBC calls Teams, check the name
Validate Client FQDNDisabled
Teams TLS profile
Teams TLS profile

Trim the cipher list. Ribbon's default client and server cipher lists include TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA, as the screenshot shows. 3DES is weak (Sweet32), and neither Teams nor Twilio needs it: Teams Direct Routing negotiates ECDHE with AES. Remove it from both lists, on the Teams profile and the Twilio one.

7.2 Listen port

Settings > SIP > Listen Port: TLS 5061 with the Teams TLS profile. (The Twilio port 5062 comes in Step 8.)

Listen ports: TLS 5061 (Teams profile) and TLS 5062 (Twilio profile)
Listen ports: TLS 5061 (Teams profile) and TLS 5062 (Twilio profile)

7.3 SIP profile (Teams)

Settings > SIP > SIP Profiles, new profile. These values come from the working 2023 config:

FieldValue
Session TimerEnable, minimum 600, offered 3600
FQDN in From HeaderSBC Edge FQDN
FQDN in Contact HeaderSBC FQDN
Trusted InterfaceEnable
UA HeaderRibbon
Origin Field Username (SDP)sbc.example.com
100relNot Present
PathNot Present
TimerRequired
UpdateSupported

Why the FQDN settings: Teams matches the SBC by the FQDN in the Contact header of its OPTIONS and INVITEs. If the Contact carries an IP, Teams ignores the SBC and the trunk never connects.

Teams SIP profile
Teams SIP profile

7.4 SIP server table (Teams)

Settings > SIP > SIP Server Tables, new table with three entries:

PriorityHostPortProtocolTLS profileMonitor
1sip.pstnhub.microsoft.com5061TLSTeamsSIP Options, keep alive 30 s, recover 5 s
2sip2.pstnhub.microsoft.com5061TLSTeamssame
3sip3.pstnhub.microsoft.com5061TLSTeamssame

Connection reuse: True, 4 sockets, forever.

Teams server table, all three Up
Teams server table, all three Up
One Teams server entry
One Teams server entry

7.5 SRTP and media list (Teams)

Settings > Media > SDES-SRTP Profiles, new profile:

FieldValue
Operation OptionRequired
Crypto SuiteAES_CM_128_HMAC_SHA1_80
Key Identifier Length (MKI)1 (what the 2023 config used; we also tested 0, no difference)
Teams SRTP profile
Teams SRTP profile

Settings > Media > Media List, new list: G.711u and G.711A, the Teams SRTP profile, DTMF RFC 2833 payload type 101. Both Teams and Twilio speak G.711, so no transcoding is needed. (Teams prefers SILK; the 2023 config led with SILK, but G.711 is supported and avoids a codec conversion.)

Teams media list
Teams media list

7.6 Signaling group (Teams)

Settings > Signaling Groups > Add SIP SG:

SectionFieldValue
Channels and routingCall Routing TableFrom Teams (Step 9)
No. of Channelsyour licensed session count
SIP ProfileTeams SIP profile
SIP Server TableTeams servers
Load BalancingPriority: Register All
Call Setup Response Timer255
MediaSupported Audio ModesDSP (plus Proxy if licensed)
Media List IDTeams media list
Play RingbackAuto on 180/183
Early 183Enable
Play Congestion ToneEnable
RTCP MultiplexingEnable
Music on HoldEnabled for SDP Inactive
SIP IP DetailsSignaling/Media Private IPEthernet 1 IP
Static NAT, public IP203.0.113.37
ICEDisabled
Listen PortsTLS 5061
Federated IP/FQDN52.112.0.0 / 255.252.0.0 and 52.120.0.0 / 255.252.0.0

The federated list is the SBC's own allow list: it only accepts SIP on this group from these ranges.

Teams signaling group, full settings
Teams signaling group, full settings

Step 8: The Twilio side of the SBC

8.1 Why Twilio needs its own port and TLS profile

This is the part that cost us the most time.

  • Teams requires mutual TLS on inbound connections, so the Teams TLS profile has Mutual Authentication on.
  • Twilio connects without a client certificate. On a mutual TLS port, Twilio's handshake fails.
  • On SBC 13.x, two signaling groups can share a port only if they share one TLS profile. Try to give them different profiles and the SBC refuses with error 31043.

So you can't put both on 5061 with different TLS settings. The fix, which is also how the 2023 box was built, is to give Twilio its own port (5062) with its own TLS profile.

8.2 TLS profile (Twilio)

FieldValueWhy
TLS ProtocolTLS 1.2 to 1.3
Mutual AuthenticationDisabledTwilio presents no client cert
CertificateSBC Edge CertificateTwilio checks your cert when it calls you
Verify Peer Server CertificateEnabledCheck Twilio's cert when you call out
Validate Server FQDNEnabled
Twilio TLS profile
Twilio TLS profile

Then add a listen port: TLS 5062 with this profile (screenshot in 7.2).

8.3 SIP profile (Twilio)

Same as the Teams one except:

FieldValueWhy
FQDN in From HeaderDisableTwilio doesn't need it
FQDN in Contact HeaderSBC FQDN
Send Assert HeaderNeverTwilio doesn't need P-Asserted-Identity from us
Trusted InterfaceDisableTwilio is outside your trust boundary
100rel / Timer / UpdateSupported
Twilio SIP profile
Twilio SIP profile

8.4 Digest credentials (Remote Authorization table)

Twilio challenges your outbound calls with digest auth, using the credential list on the trunk. Settings > SIP > Remote Authorization Tables, new table, one entry:

FieldValueWhy
RealmblankAnswer any realm
Authentication IDyour credential list username
Passwordfrom your vault
From URI User MatchRegex .*The default only answers challenges whose From user equals the username, which never happens with +E.164 callers
Remote authorization entry (username masked)
Remote authorization entry (username masked)

Gotcha: when you rotate the trunk password in Twilio, update this entry at the same time.

8.5 SIP server table (Twilio)

One entry: your-trunk.pstn.twilio.com, port 5061, TLS, the Twilio TLS profile, SIP Options monitoring, and the Remote Authorization table from 8.4.

Note the asymmetry: Twilio calls you on 5062, you call Twilio on 5061.

Twilio server entry
Twilio server entry

8.6 SRTP and media list (Twilio)

SRTP profile: Required, AES_CM_128_HMAC_SHA1_80, MKI length 0 (Twilio's guide says 0 to disable MKI).

Twilio SRTP profile
Twilio SRTP profile

Media list: G.711u and G.711A, the Twilio SRTP profile, RFC 2833 payload type 101.

Twilio media list
Twilio media list

8.7 Signaling group (Twilio)

Like the Teams group, with these differences:

FieldValue
Call Routing TableFrom Twilio
SIP ProfileTwilio SIP profile
SIP Server TableTwilio
Media ListTwilio media list
Play Congestion Tone / Music on Hold / RTCP muxDisable
Listen PortTLS 5062
Federated IP/FQDNall 15 Twilio signaling ranges from Step 5
Twilio signaling group, full settings
Twilio signaling group, full settings

8.8 On the Twilio side

  1. Secure Trunking: on (TLS plus SRTP).
  2. Termination URI: your-trunk.pstn.twilio.com (this is what the SBC calls). Attach your credential list under termination authentication.
  3. Origination URI: sip:sbc.example.com:5062;transport=tls (this is where Twilio sends inbound calls). Mind the port.
  4. Numbers: attach your numbers to the trunk.

If you also send calls from Twilio Programmable Voice (a webhook that returns <Dial><Sip>), add ;secure=true so Twilio offers SRTP. Without it, the call arrives with plain RTP and the SBC's "Required" SRTP profile rejects it:

<Response>
  <Dial answerOnBridge="true">
    <Sip>sip:+15550100000@sbc.example.com:5062;transport=tls;secure=true</Sip>
  </Dial>
</Response>

Step 9: Number transformations and call routes

Teams expects numbers in +E.164. Twilio is happy with +E.164 too. So both transformation tables just normalize to +E.164.

Settings > Call Routing > Transformation, two tables. All entries are Optional (Match One):

#FieldInput regexOutput
1Called Address/Number^([2-9]\d{9})$+1\1
2Called Address/Number^\+?(1[2-9]\d{9})$+\1
3Called Address/Number(.*)\1 (passthrough, e.g. international)
4Calling Address/Number^([2-9]\d{9})$+1\1
5Calling Address/Number^\+?(1[2-9]\d{9})$+\1
6Calling Address/Number(.*)\1 (anonymous etc.)
7Calling Extension (Teams to Twilio only)(.*)empty (strip ;ext=)
Twilio to Teams
Twilio to Teams
Teams to Twilio
Teams to Twilio

Settings > Call Routing > Call Routing Table, two tables:

TableUsed byEntryDestinationTransformationMedia
From TwilioTwilio SGall numbersTeams SGTwilio to TeamsTeams media list, Audio Stream Mode DSP, transcoding enabled
From TeamsTeams SGall numbersTwilio SGTeams to TwilioTwilio media list, DSP, transcoding enabled
Route from Twilio to Teams
Route from Twilio to Teams
Route from Teams to Twilio
Route from Teams to Twilio

Each signaling group points at the table named after where calls come from.

Step 10: Pair the SBC in Teams

In Teams PowerShell:

Connect-MicrosoftTeams

# 1. Register the SBC. The FQDN must be under a verified tenant domain.
New-CsOnlinePSTNGateway -Fqdn sbc.example.com -SipSignalingPort 5061 `
  -Enabled $true -MaxConcurrentSessions <licensed sessions>

# 2. A PSTN usage, a voice route that uses the SBC, and a policy.
Set-CsOnlinePstnUsage -Identity Global -Usage @{Add="Twilio-US"}
New-CsOnlineVoiceRoute -Identity "Twilio-US-All" -NumberPattern ".*" `
  -OnlinePstnGatewayList sbc.example.com -OnlinePstnUsages "Twilio-US" -Priority 1
New-CsOnlineVoiceRoutingPolicy -Identity "Twilio-US" -OnlinePstnUsages "Twilio-US"

# 3. Give a user a number on Direct Routing and the policy.
Set-CsPhoneNumberAssignment -Identity user@example.com `
  -PhoneNumber +15550100000 -PhoneNumberType DirectRouting
Grant-CsOnlineVoiceRoutingPolicy -Identity user@example.com -PolicyName "Twilio-US"

Then open Teams admin center > Voice > Direct Routing and look at the SBC's health:

What it saysWhat it means
Active, greenTeams is getting OPTIONS from the SBC and the SBC answers Teams' OPTIONS
Inactive. The trunk never connectedThe SBC has never opened a TLS connection to Teams. Look at interface binding (Step 1), routes (Step 6), certificates (Steps 3 and 4) and the Contact FQDN (Step 7.3)
Warning about OPTIONSTLS works but OPTIONS go unanswered one way. Usually NSG or the federated IP list

It can take up to 15 minutes after the SBC is fixed for the status to update.

Step 11: Check health the right way

We lost hours trusting the wrong counters. Here's what to believe:

SignalTrust it?Notes
Signaling group counters rt_OutOptions / rt_In2xxRespNoThey kept climbing while the groups were bound to mgt0 and nothing was actually leaving the box
SIP server events "SIP Server Not Responding" / "Became Responsive"YesFlapping means routing or NSG trouble
SIP server counters: transactions vs transaction failuresYesFailures should stay at 0
Monitor tab, signaling groups green, server table "Up"Yes, as a first check
Teams admin center healthYesThe only view from Microsoft's side
A real test call, both directionsThe only real proof
Monitor tab: both signaling groups up, all channels idle (green)
Monitor tab: both signaling groups up, all channels idle (green)
Signaling group table
Signaling group table

For deeper digging, the SBC can capture packets on Ethernet 1 (Diagnostics tab, or REST /rest/packetcapture?action=startcapture). Remember everything is TLS, so you'll see sizes and timing, not SIP text.

Step 12: Licensing and the known open issue

What the trial gives you

A fresh SWe Lite runs on a 30 day embedded trial license:

FeatureTrial
SIP Signaling Sessions5
Enhanced Media Sessions with Transcoding (DSP)3
Enhanced Media Sessions without TranscodingNot licensed
Proxy Local SRTP0
SIP Registrations5
Current licenses on the trial
Current licenses on the trial

With SRTP on both legs, the SBC has to decrypt and re-encrypt media. With 0 Proxy Local SRTP licensed, every call must use a DSP session, which caps the trial at 3 concurrent calls.

Licenses are locked to the VM

Ribbon SWe Lite licenses are tied to a hardware ID (SweLiteId, shown in the license page as <hardware-id>). If you rebuild the VM, the ID changes and your license stops fitting ("Failed To Apply License: SWe Edge license has incorrect fingerprint"). Ribbon support has to rehost it to the new ID. Ask them to include enough media sessions (DSP, or Proxy Local SRTP) for your SRTP to SRTP call count, not just SIP sessions.

Known open issue

With everything above in place, inbound Twilio calls reach the SBC on 5062 and the SBC replies 100 Trying, then 503 within about 2 ms. Twilio logs error 32011. Packet captures show no INVITE going to Teams. The call is refused inside the SBC before the Teams leg starts.

Things we tried that did not change it: switching media to proxy mode, Teams MKI 1 vs 0, setting the private media source IP, turning off all DSP features on the route, and putting the Teams signaling group on the Twilio SIP profile.

An instant local reject with no outbound INVITE fits a license or resource check, so our lead suspect is the trial's missing media license types for SRTP to SRTP. We're retesting once the rehosted license is applied, and will update this article with the result. If you hit the same 503, check your license page first.

---

Troubleshooting

SymptomWhere you see itCauseFix
"Inactive. The trunk never connected"Teams admin centerSBC never opened TLS to Teams: SGs bound to mgt0, no default route via Ethernet 1, bad cert chain, or IP in ContactTwo NICs (Step 1), routes (6.1), certs (3, 4), FQDN in Contact (7.3), bounce SGs
SGs configured but bound to mgt0; event "this SG needs to be bounced manually"SBC alarms/eventsSingle NIC VM, or interface changed without a bounceAdd the WAN NIC; disable and re-enable each SG
Error 14026Setting next hop on Ethernet 1Interface is on DHCPUse a static default route via the WAN gateway (6.1)
Error 31043Adding the second SG to a TLS portTwo SGs on one port with different TLS profiles (13.x)Separate ports: Teams 5061, Twilio 5062 (Step 8)
Error 15039Certificate importLegacy PKCS12 encryptionRebuild with AES-256-CBC and SHA-256 MAC (3.2)
Error 15026REST certificate importMultipart uploadSend base64 in form fields (3.2)
Certificate chain errors, Verify Status not OKSBC certificate page, TLS failuresMissing root or intermediateImport the roots in Step 4; check chains with openssl s_client
Twilio 32011Twilio call logsTwilio couldn't complete the call to your SBC: wrong port in origination URI, NSG, mutual TLS on Twilio's port, or the SBC rejectingOrigination ...:5062;transport=tls, NSG for all Twilio ranges, Twilio TLS profile without mutual auth, check the SBC's response
503 from the SBC right after 100 TryingTwilio logs, SG "egress calls rejected" counterSBC refused the call before sending to Teams; in our case suspected license/resourcesSee Step 12. Check licenses, channel counts, media lists
Servers flap "Not Responding" / "Became Responsive"SBC eventsSome traffic leaving through mgt0Default route via Ethernet 1 (6.1)
Programmable Voice call rejectedSBC<Dial><Sip> sent plain RTP to a "Required" SRTP profileAdd ;secure=true to the SIP URI
"Src ip does not match proxyset"Earlier on AudioCodes Live Hub, same root cause appliesTwilio retried from an edge that wasn't allowedAllow every Twilio signaling range (Step 5)
"Restricted Dest Number" / "Restricted Source Number"Earlier on AudioCodes Live HubThe number wasn't on the hosted SBC's allowed listOn a hosted SBC, add the number; on your own Ribbon, check transformations and routes
Web UI unreachable after adding the second NICBrowserDefault route now points at Ethernet 1, or the public IP movedUse the admin public IP; add the /32 route to your admin IP via the mgmt gateway (6.1)

What the official guide covers, and what it doesn't

The Twilio and Ribbon interop guide ("Ribbon SBC Edge SWe Lite R9.0 on AWS Interop with Cisco UCM and Microsoft Teams Direct Routing for Twilio Elastic SIP Trunking") is worth reading. It covers:

  • Viewing licenses and importing trusted CA certificates.
  • The logical interfaces (Admin IP, Ethernet 1, Ethernet 2) and the concept of static routes.
  • The Easy Config wizard for "SIP Trunk to Microsoft Teams", then manual fixes the wizard misses: ringback on 180/183, Early 183, static NAT, OPTIONS monitoring, session timers, Send Assert Header Never and Trusted Interface off on the Twilio profile.
  • Moving the Twilio trunk to TLS and SRTP (crypto suite, MKI 0, TLS listen port).
  • Transformation tables, call routes, and a Privacy header rule so Teams callers aren't sent to Twilio as anonymous.
  • The Twilio console: IP ACL, trunk, termination and origination URIs, numbers.

Where it falls short for Azure and for this exact setup:

TopicGuideWhat you actually need
PlatformAWS, with SBC deployment links out to Ribbon docsAzure image, Gen1 VM sizes, quota and capacity checks
InterfacesAssumes data interfaces exist; its lab uses Ethernet 1 for Twilio and Ethernet 2 for TeamsSays nothing about the fact that mgt0 never carries SIP, or that a single-NIC Azure VM silently breaks everything. You need a second NIC
RoutingExplains what a static route isThe specific default route via the WAN gateway, the /32 back to your admin IP, and why you can't set next hop on DHCP (14026)
NAT"Enable Static NAT and map the respective IP addresses"Which public IP goes where in Azure, and that SGs must be bounced after interface changes
Ports and TLSTwilio on UDP 5060 first, then TLS 5061; Teams via the wizard13.x forbids different TLS profiles on one port (31043); Teams needs mutual TLS, Twilio can't do it, so Twilio needs its own port and profile
Twilio authIP ACLWe used a credential list with digest auth, which needs the Remote Authorization table with a .* From match
Certificates"Import root and intermediates"Which roots Teams and Twilio chain to today, and how to check
Teams sideA link to Ribbon's Teams articleThe FQDN requirements, PowerShell pairing and how to read Teams health
HealthMonitor tab and test callsWhich counters lie, and which events to trust
Licensing"Acquire cloud SIP sessions"Trial limits, SRTP media licensing, hardware-ID locking and rehosting

Checklist

Azure

  • Marketplace terms accepted; Gen1 compatible VM size with quota and capacity
  • Two subnets (mgmt 10.8.2.0/24, WAN 10.8.3.0/24), two NICs, mgmt NIC primary
  • Call public IP on the WAN NIC, separate admin public IP on the mgmt NIC
  • NSG (WAN): TCP 5061 from both Microsoft /14s; UDP media range from both Microsoft /14s; TCP 5062 from all Twilio signaling ranges; UDP media range from 168.86.128.0/18
  • NSG (mgmt): TCP 443 from your admin IP only

DNS and certificates

  • sbc.example.com points to the call IP; domain verified in Microsoft 365
  • Public cert with the SBC FQDN as SAN, ISRG Root X1 chain, imported as SBC Primary Certificate, Verify Status OK
  • Trusted CAs: DigiCert Global Root CA and G2, Microsoft 2017 and G2 roots, ISRG Root X1 and your intermediates
  • Renewal reminder set (90 days)

SBC networking

  • Ethernet 1 up with the WAN private IP
  • Static route 0.0.0.0/0 via the WAN gateway; /32 to your admin IP via the mgmt gateway; survives a reboot
  • DNS 168.63.129.16; host name and domain set
  • Media port range matches the NSG

Teams leg

  • TLS profile: 1.2 to 1.3, mutual auth on; listen port TLS 5061
  • SIP profile: FQDN in From and Contact, Timer required
  • Server table: sip, sip2, sip3 on TLS 5061 with OPTIONS
  • SRTP required, AES_CM_128_HMAC_SHA1_80; G.711 media list
  • SG: Ethernet 1, static NAT to the call IP, federated Microsoft ranges, ringback auto 180/183, Early 183, RTCP mux

Twilio leg

  • Own TLS profile without mutual auth; listen port TLS 5062
  • Server table: your-trunk.pstn.twilio.com TLS 5061, remote auth table with .* match
  • SRTP required, MKI 0; G.711 media list
  • SG: Ethernet 1, static NAT, federated Twilio ranges
  • Twilio trunk: secure trunking, origination sip:sbc.example.com:5062;transport=tls, credential list, numbers attached
  • Programmable Voice <Sip> URIs include ;secure=true

Routing and Teams

  • Transformation tables to +E.164 both ways; routes From Twilio to Teams and From Teams to Twilio
  • SGs bounced after interface changes; both green
  • New-CsOnlinePSTNGateway, PSTN usage, voice route, voice routing policy, user number assigned
  • Teams admin center shows the SBC active

Proof

  • No "Not Responding" flaps; transaction failures at 0
  • License covers your SIP sessions and SRTP to SRTP media sessions, on the current hardware ID
  • A real call in both directions, with two-way audio

Outgrown your technology?

Tell me what's going on. I read every message personally and usually reply within a day.

Start a conversation → Take the free scorecard