Microsoft 365 Email & Collaboration

Deploying Teams Phone with Direct Routing: SBC Pairing, Voice Routing Policies, and Dial Plans

Direct Routing is the only Teams PSTN option where you own the trunk. Microsoft hands you a SIP peering surface in Azure; everything from the certificate on the Session Border Controller (SBC) to the regex on a voice route is yours to get right. That control is the reason to choose it, and also why a misordered PSTN usage or an unsigned certificate quietly drops calls in production — no error dialog, just a caller hearing dead air or a fast-busy. This guide builds the full call path in the exact order the platform evaluates it: certified SBC, PSTN gateway, voice routes, PSTN usages, voice routing policy, phone number, tenant dial plan, and emergency calling — then layers on media bypass, local media optimization, branch survivability, and the monitoring you need to prove it works.

Teams Phone is Microsoft’s cloud PBX: presence-integrated calling, voicemail, call queues, auto attendants, and PSTN breakout, all through the Teams client and certified handsets. Direct Routing connects that cloud PBX to the outside telephone network through your SIP trunk, terminated on a Session Border Controller Microsoft has certified. Where Calling Plans and Operator Connect hide the trunk entirely, Direct Routing exposes every layer — precisely what a contact-center integration, an analog paging system, or a decade-long carrier contract needs.

By the end you will be able to pick Direct Routing deliberately (not by default), pair an SBC without the certificate and firewall mistakes that eat the first day, author a voice-routing plan whose usage order you can defend, voice-enable users, wire dynamic E911 so an emergency call reaches the right dispatcher with the right address, decide whether media bypass and local media optimization belong in your topology, deploy a Survivable Branch Appliance for site outages, and read Call Analytics and the Call Quality Dashboard to localize a bad call to a single hop. Everything below assumes the modern MicrosoftTeams PowerShell module. The legacy Skype for Business Online connector is retired; install and connect once:

Install-Module MicrosoftTeams -Scope CurrentUser
Connect-MicrosoftTeams

What problem this solves

You have Teams and you have a phone system. The pain is joining them without giving up the two things that made the phone system valuable: your existing carrier economics and your existing telephony estate. A managed option (Calling Plans, Operator Connect) is a clean answer only when Microsoft or a partner sells numbers in your country, your call patterns are simple PSTN-in/PSTN-out, and you have nothing analog or contact-center-shaped to integrate. The moment any of those is false — Microsoft has no Calling Plan in-country, you signed a three-year SIP trunk contract last year, your warehouse has overhead paging on an analog interface, or your contact center sits behind a Genesys/Five9 SBC — a managed plan cannot reach it and Direct Routing is the only path.

What breaks without a correct Direct Routing build is subtle and expensive because telephony fails quietly. A certificate whose chain Microsoft does not trust means the SBC never appears healthy and no call ever completes, but there is no obvious error — the SBC health dashboard simply shows nothing. A voice route with a .* catch-all placed above a specific route sends every international call down the wrong (or a disabled) trunk. A PSTN usage in the wrong order inside a voice routing policy silently routes internal contact-center queue calls out to the public network and back, breaking a compliance boundary and doubling the cost. A dynamic-location misconfiguration means a 911 call from the third floor reaches a dispatcher with the address of the corporate headquarters two states away — a life-safety failure, and in the US a legal one under Kari’s Law and RAY BAUM’S Act.

Who hits this: enterprises consolidating regional PBXs onto Teams, multinationals where Microsoft’s Calling Plan coverage is patchy, any organization with an incumbent carrier contract or a contact-center platform, and telecom/UC teams migrating away from Skype for Business Enterprise Voice. It is an Advanced topic because success is not “a call connected once” — it is a routing plan you can reason about, an emergency-calling design that survives an audit, media that flows where compliance requires, and a survivability story for the day the WAN link to a branch dies.

Learning objectives

By the end of this article you can:

Prerequisites & where this fits

You should hold at least the Teams Administrator role (or Global Administrator) and be comfortable in the MicrosoftTeams PowerShell module — every operation in this article is scriptable and most are only practical in PowerShell. You need one or more Teams Phone licenses (the standalone Teams Phone add-on, or a suite such as E5 that includes it) assigned to the users you will voice-enable, plus a certified SBC (physical, virtual, or a hosting provider’s) and the carrier SIP trunk behind it. Familiarity with SIP (INVITE, 18x/200/BYE, headers like From, P-Asserted-Identity, Diversion), TLS certificates, E.164 number formatting, and .NET regular expressions is assumed — this is not an introduction to telephony.

This sits in the Teams Phone / Voice track and is downstream of tenant readiness: you should already have DNS, licensing and network egress in order. It pairs tightly with Teams Governance: Lifecycle, Naming, Expiration & Sensitivity Labels for the broader Teams-admin surface, and with Exchange Online Mail Flow: Transport Rules & Connectors Deep Dive because Teams voicemail and calendar integration ride on Exchange. Where you gate who may even reach the Teams admin surface, Entra Conditional Access at Scale: Personas & Authentication Context is upstream. For the QoS marking and network segmentation that keep media clean, Micro-Segmentation with NSGs, ASGs & Application-Tier Isolation covers the transport layer these calls traverse.

A quick map of who owns what during a Direct Routing incident, so you call the right person fast:

Layer What lives here Who usually owns it Failure classes it can cause
Teams client / Entra Sign-in, policy assignment, presence Teams / identity admin No dial pad (not voice-enabled), wrong policy applied
Teams admin / routing Gateways, routes, usages, policies, dial plans Teams voice admin Wrong route, dropped/misrouted calls, no PSTN option
Microsoft SIP proxy sip.pstnhub.microsoft.com TLS + SIP Microsoft (platform) 500/503 from the cloud side; health outages
SBC TLS, SIP signaling, media anchoring, transcoding Telecom / UC team No OPTIONS (cert/firewall), one-way audio, 488 codec
Carrier SIP trunk PSTN breakout, CLID, number ranges Carrier / telco vendor 403/404 from carrier, no inbound, CLID rejected
Network / firewall Subnets, ports, QoS, NAT Network team Blocked 5061/media, one-way audio, jitter/loss

Core concepts

Six mental models make every later decision obvious.

The three PSTN options are three different ownership boundaries, and they compose. Calling Plans put Microsoft on the trunk; Operator Connect puts a partner carrier on it through the admin center; Direct Routing puts you on it via an SBC you pair and routes you author. All three can coexist in one tenant, and even on one user — a user with a Calling Plan license and a voice routing policy routes patterns that match a voice route down the SBC and everything else through the Calling Plan, which always applies as the implicit last route. Choosing Direct Routing is choosing to own the trunk because a managed option cannot reach what you need to reach.

The call path is evaluated in a fixed order, and you build it in reverse. When a user dials, Teams first runs the dialed string through the user’s tenant dial plan (normalization to E.164), then consults the user’s voice routing policy (an ordered list of PSTN usages), walks those usages top-to-bottom, and for the first usage whose voice routes produce a pattern match, picks the lowest-priority route and hands the call to one of that route’s PSTN gateways (SBCs). You author it bottom-up: gateway first (nothing routes without it), then usages (the containers), then routes (the matching), then policies (the ordered assignment), then the number and dial plan on the user.

The SBC is a certified, trusted peer or it is invisible. Microsoft trusts an SBC by two things: a certificate whose full chain roots in the Microsoft Trusted Root Certificate Program with the SBC’s FQDN in the CN or SAN, and a domain for that FQDN verified in your tenant (never *.onmicrosoft.com). Health rides on SIP OPTIONS — the SBC and the Microsoft proxy ping each other; if OPTIONS do not flow (bad chain, firewall, wrong port), the SBC shows unhealthy or blank and no call completes, with no obvious error.

PSTN usages are just named strings whose order is the control plane. A usage carries no logic of its own — it is a label that links voice routes (which reference it) to voice routing policies (which list it in order). Because a voice routing policy is evaluated first match wins, the order in which usages appear in the policy decides which route is even considered. Getting the order wrong is the single most common routing bug, and it fails silently: calls go down a trunk you did not intend.

Media flows through the SBC’s media processor, through Microsoft’s media processors, or directly — and you choose. By default, media (RTP/SRTP) between the Teams client and the SBC traverses Microsoft’s media processors in the cloud. Media bypass removes that hop so RTP flows directly between the Teams client and the SBC. Local media optimization (LMO) goes further for multi-site topologies, keeping media on a downstream SBC local to the user even when signaling anchors on a central proxy SBC. Each is a performance and compliance lever with a real troubleshooting cost.

Emergency calling is two policies plus a location service, and Direct Routing adds one gateway flag. Emergency call routing policy defines which strings are emergency numbers and which PSTN usage (hence trunk) they take; emergency calling policy defines who gets notified and how; the Location Information Service (LIS) maps network identifiers (subnet, WAP, switch/port) to civic addresses so the location is dynamic. For Direct Routing you additionally set -PidfLoSupported $true on the egress gateway so Teams sends the PIDF-LO XML location payload the SBC relays to the emergency provider.

The vocabulary in one table

Before the deep sections, pin down every moving part. The glossary at the end repeats these for lookup; this table is the mental model side by side:

Concept One-line definition Where it lives Why it matters to routing
SBC / PSTN gateway Certified device terminating your SIP trunk On-prem / cloud / hoster Nothing routes without a paired, healthy gateway
PSTN usage A named string linking routes to policies Global object (list) Its order in a policy decides routing
Voice route Regex → gateway(s) mapping Global object Matches the called number, picks the trunk
Voice routing policy Ordered list of usages, assigned to users Per-user policy First matching usage wins
Tenant dial plan Ordered normalization rules → E.164 Per-user policy Turns dialed strings into routable numbers
Normalization rule Pattern/Translation pair Inside a dial plan Reshapes a dialed string
Online voice routing policy Same as voice routing policy (Teams term) Per-user The policy that selects the SBC path
Media bypass RTP direct: client ↔ SBC Gateway + policy Removes the Microsoft media hop
Local media optimization Media stays on a local SBC Network site config Keeps media on-net across sites
LIS Network-ID → civic address map Tenant location DB Dynamic E911 address discovery
Emergency call routing policy Emergency numbers + their usage Per-user (DR) Which string is 911, which trunk
SBA Survivable Branch Appliance Branch SBC Basic calling during a WAN outage
CQD Call Quality Dashboard Teams admin center Tenant-wide media-quality trend
Call Analytics Per-call, per-leg diagnostics Teams admin center One call, hop by hop

The three PSTN options, and where Direct Routing fits

Before touching an SBC, be deliberate about why you are not using a managed option. All three can coexist in one tenant, even on one user.

Option Who runs the trunk Setup surface Number source Best when
Calling Plans Microsoft Buy a license, assign a number Microsoft (country-limited) Microsoft sells numbers in your country and you want zero telco ops
Operator Connect A participating carrier Carrier provisions through the Teams admin center The carrier You want a carrier contract but no SBC to manage
Direct Routing You (or a hosting provider) Pair a certified SBC, author all routing Your carrier / existing DIDs Microsoft has no Calling Plan in-country, you have an existing carrier contract, or you must reach analog / PBX / contact-center gear

The decision comes down to country coverage and existing telephony assets. A depreciating contact center, analog overhead paging, or an active SIP trunk contract all argue for Direct Routing. Mixing is legitimate: a user with a Calling Plan license and a voice routing policy routes matching patterns down the SBC and everything else through the Microsoft Calling Plan, which always applies as the implicit last route.

The trade-offs that actually decide it, side by side:

Dimension Calling Plans Operator Connect Direct Routing
Trunk ownership Microsoft Carrier You
SBC to manage None None Yes (certified)
Country coverage Where Microsoft sells Where the operator sells Anywhere your carrier reaches
Existing-contract reuse No Only with a participating operator Yes — any SIP carrier
Reach analog / PBX / contact center No No Yes
Routing control (dial plans, routes) Minimal Minimal Full
Emergency calling Managed / dynamic Operator-provided You wire LIS + PidfLo
Media bypass / LMO N/A N/A Available
Time to first call Fastest Fast Slowest (build + certify)
Ongoing telco ops burden Lowest Low Highest
Survivability (SBA) N/A N/A Available

And a plain decision table — match the constraint to the option:

If you have… Then choose… Because
Microsoft Calling Plans in every country you operate, simple call patterns Calling Plans Zero telco ops; numbers from Microsoft
A preference for a carrier contract but no wish to run an SBC Operator Connect Carrier owns the trunk, you provision in-portal
An in-force SIP trunk contract you can’t exit Direct Routing Reuse the trunk and its economics
A contact center / IVR / paging behind a SIP or analog interface Direct Routing Only DR can reach non-Microsoft gear
Countries with no Microsoft or operator coverage Direct Routing Your carrier defines coverage
A compliance rule that PSTN media stay on your network Direct Routing + media bypass RTP goes client ↔ SBC, not via Microsoft
A branch that must keep calling through a WAN outage Direct Routing + SBA Survivability is DR-only

Step 1 — SBC prerequisites: domains, certificate, FQDN, firewall

The single most common pairing failure is a domain that was never registered. The SBC’s FQDN must sit under a domain added to your tenant — you cannot use the *.onmicrosoft.com default, and the domain must be verified before pairing.

# The SBC FQDN domain must be one of these (not *.onmicrosoft.com)
Get-CsTenant | Select-Object -ExpandProperty Domains

If the SBC is sbc01.sip.contoso.com, the domain sip.contoso.com must be verified in the tenant and have at least one licensed user under it. Provisioning can take up to 24 hours to propagate. A common subtlety: the base domain (the registrable domain, contoso.com) must be verified; a subdomain FQDN under it (sbc01.sip.contoso.com) is then usable without separately verifying sip.contoso.com, provided contoso.com itself is verified.

The certificate rules are non-negotiable and trip up most first deployments:

The certificate requirements as a checklist you can hand to whoever requests it:

Requirement Value / rule Why How it fails if wrong
Subject CN or SAN The exact SBC FQDN TLS name match Handshake name mismatch; no OPTIONS
Wildcard scope *.contoso.com = one label only RFC 2818 sbc.eu.contoso.com not covered → fails
Issuing CA In Microsoft Trusted Root Program Cloud trusts the leaf Untrusted chain; TLS rejected
Chain completeness Leaf + all intermediates on the SBC Path building Missing intermediate; intermittent failures
EKU Server Authentication present TLS server role Rejected at handshake
Key length ≥ 2048-bit RSA Modern crypto floor CA won’t issue / cloud rejects
Validity / renewal Track expiry; renew ahead Expired cert = dead trunk Silent trunk-wide outage on expiry

The SBC terminates TLS to three Microsoft SIP proxy FQDNs, tried in order: sip.pstnhub.microsoft.com (global, always first), sip2.pstnhub.microsoft.com (secondary), and sip3.pstnhub.microsoft.com (tertiary). Your firewall must permit all three, and inbound SIP can originate from any IP in the subnets below, not only the resolved ones.

The firewall facts to pin down before the network team asks:

Purpose Direction Protocol / port Source / destination Notes
SIP signaling to Microsoft SBC → cloud TCP/TLS 5061 Dest: SIP proxy FQDNs Global FQDN tried first
SIP signaling from Microsoft cloud → SBC TCP/TLS, src 1024–65535 Src: 52.112.0.0/14, 52.120.0.0/14 Any IP in the subnet, not just resolved
Media (no bypass) both UDP/SRTP Media-processor ranges 3478–3481, 49152–53247 Skipped entirely with media bypass
Media (bypass) both UDP/SRTP Direct client ↔ SBC Client-network paths must allow it
SIP OPTIONS (health) both over 5061/TLS between SBC and proxy Drives the SBC health dashboard

GCC High and DoD use different single FQDNs and subnets, and there the SBC must listen on 5061 specifically. Commercial cloud signaling and media both use subnets 52.112.0.0/14 and 52.120.0.0/14. SIP/TLS from the SBC targets destination port 5061; the SIP proxy replies from source ports 1024–65535. Without media bypass, media (UDP/SRTP) uses media-processor ports 3478–3481 and 49152–53247.

Step 2 — Pair the SBC: create the PSTN gateway

Pairing is a single cmdlet, but the defaults matter. New-CsOnlinePSTNGateway creates the gateway disabled unless you pass -Enabled $true, and the parameter is SipSignalingPort (the old double-l spelling SipSignallingPort was renamed).

New-CsOnlinePSTNGateway `
  -Fqdn sbc01.sip.contoso.com `
  -SipSignalingPort 5061 `
  -Enabled $true `
  -SendSipOptions $true `
  -MaxConcurrentSessions 500 `
  -ForwardCallHistory $true `
  -ForwardPai $true `
  -FailoverTimeSeconds 10 `
  -MediaBypass $false `
  -Description "Primary SBC - DC1 carrier trunk"

What each non-default choice buys you:

The full New-CsOnlinePSTNGateway parameter reference — the ones you will actually touch:

Parameter What it controls Default When to change Gotcha
-Fqdn The SBC’s fully qualified name — (required) Always Must match the cert CN/SAN and a verified domain
-SipSignalingPort TCP port for SIP/TLS 5061 typical Rare Note single-l spelling; must match the SBC listener
-Enabled Whether the gateway takes calls $false Always set $true to go live Forgetting this is the classic “paired but no calls” bug
-SendSipOptions Exchange SIP OPTIONS for health $true Leave on Off = no health data, no alerting
-MaxConcurrentSessions Alerting threshold for concurrent calls unset Set to trunk capacity Alerts at 90%; does not enforce a cap
-MediaBypass Allow direct client↔SBC media $false Enable per topology SBC must also support it and share media subnet
-ForwardPai Send P-Asserted-Identity $false Carrier needs identity/billing Needed for correct CLID on anonymous
-ForwardCallHistory Send History-Info / Referred-By $false Forward/transfer scenarios Carrier sees the diversion chain
-FailoverTimeSeconds Wait before trying next trunk 10 Slow networks only Too high delays failover
-FailoverResponseCodes SIP codes that trigger failover 408, 503, 504 Add carrier-specific codes Wrong codes = no failover on real faults
-PidfLoSupported Send PIDF-LO location XML $false Emergency-egress gateway Required for dynamic E911 over DR
-MediaRelayRoutingLocationOverride Force media-relay region unset Fix suboptimal media path Only with a real geography reason
-GatewaySiteId Bind gateway to a network site unset Local media optimization Ties into -BypassMode
-BypassMode Always / OnlyForLocalUsers Always LMO topologies Controls when bypass applies per site

To drain an SBC for maintenance, set it disabled — existing calls survive, new calls route elsewhere:

Set-CsOnlinePSTNGateway -Identity sbc01.sip.contoso.com -Enabled $false

Step 3 — Validate the pairing and SIP health

Confirm the gateway is present and enabled before building anything on top of it. The output should show your FQDN with Enabled : True.

Get-CsOnlinePSTNGateway | Format-List Identity, Enabled, SipSignalingPort, MediaBypass, MaxConcurrentSessions, PidfLoSupported

Health monitoring rides on SIP OPTIONS. With SendSipOptions $true, the SBC health dashboard in the Teams admin center (Voice → Direct Routing) shows TLS connectivity, SIP OPTIONS status, and concurrent-call counts per gateway. If a freshly paired SBC shows no OPTIONS traffic, the cause is almost always the certificate chain, a firewall blocking 5061 inbound, or the SBC’s TLS context not trusting Microsoft’s root CAs. The dashboard lags real time by a few hours.

The health signals and what each state means:

Signal Healthy value Unhealthy cause Where to confirm
TLS connectivity Connected Bad chain, expired cert, wrong port SBC health dashboard; SBC TLS logs
SIP OPTIONS Active (200 OK both ways) Firewall on 5061, OPTIONS disabled Dashboard; SendSipOptions on gateway
Concurrent calls Below MaxConcurrentSessions Trunk saturated Dashboard call-count tile
Gateway Enabled True Created without -Enabled $true Get-CsOnlinePSTNGateway

Step 4 — PSTN usages, voice routes, and voice routing policies

The layering is precise. PSTN usages are named containers; voice routes match a dialed-number regex and point at gateways; voice routing policies are an ordered list of usages assigned to users. Build bottom-up.

The three objects, and how they relate:

Object Cmdlet family Holds References Assigned to
PSTN usage *-CsOnlinePstnUsage A list of name strings Nothing — (referenced by routes and policies)
Voice route *-CsOnlineVoiceRoute NumberPattern, gateways, usages, priority Usages + gateways
Voice routing policy *-CsOnlineVoiceRoutingPolicy An ordered list of usages Usages Users (Grant-)

4a. Create PSTN usages

Usages are strings on the global object. Order inside a policy is what matters later, so name them by scope.

Set-CsOnlinePstnUsage -Identity Global -Usage @{Add="US and Canada"}
Set-CsOnlinePstnUsage -Identity Global -Usage @{Add="International"}
Set-CsOnlinePstnUsage -Identity Global -Usage @{Add="USE911"}

# Confirm (the list can be truncated; expand it):
(Get-CsOnlinePstnUsage).Usage

4b. Create voice routes

A route ties a NumberPattern (a .NET regex against the called number in E.164) to a OnlinePstnGatewayList. Lower Priority wins; SBCs within a route are tried in random order. Build an active route, a backup at lower priority, and a catch-all.

# Active route for Seattle area codes -> primary SBCs
New-CsOnlineVoiceRoute -Identity "Redmond 1" `
  -NumberPattern "^\+1(425|206)(\d{7})$" `
  -OnlinePstnGatewayList sbc01.sip.contoso.com, sbc02.sip.contoso.com `
  -Priority 1 -OnlinePstnUsages "US and Canada"

# Backup route, same pattern, secondary SBCs
New-CsOnlineVoiceRoute -Identity "Redmond 2" `
  -NumberPattern "^\+1(425|206)(\d{7})$" `
  -OnlinePstnGatewayList sbc03.sip.contoso.com, sbc04.sip.contoso.com `
  -Priority 2 -OnlinePstnUsages "US and Canada"

# Catch-all for the rest of US/Canada
New-CsOnlineVoiceRoute -Identity "Other +1" `
  -NumberPattern "^\+1(\d{10})$" `
  -OnlinePstnGatewayList sbc01.sip.contoso.com, sbc02.sip.contoso.com `
  -Priority 3 -OnlinePstnUsages "US and Canada"

# Everything else (international), its own usage
New-CsOnlineVoiceRoute -Identity "International" `
  -NumberPattern ".*" `
  -OnlinePstnGatewayList sbc01.sip.contoso.com, sbc02.sip.contoso.com `
  -Priority 4 -OnlinePstnUsages "International"

The voice-route fields and how they behave:

Field Meaning Selection rule Common mistake
NumberPattern .NET regex on the E.164 called number Must match to be eligible Unanchored / greedy .* swallowing specifics
Priority Order among routes in a usage Lowest number wins Backup given a lower priority than active
OnlinePstnGatewayList SBCs this route uses Tried in random order within the route Assuming a first-listed gateway is preferred
OnlinePstnUsages Usages this route belongs to Links route → policy Route not attached to any used usage

Two failure modes to internalize. An invalid regex in NumberPattern silently fails to match — test patterns against real numbers first. And the extension is stripped before matching: +14255550100;ext=123 matches as +14255550100.

4c. Create and order voice routing policies

The policy is an ordered list of usages. Usages are evaluated top to bottom and the first match wins — later usages are never consulted. Put the most specific usage first.

# US-only users: one usage
New-CsOnlineVoiceRoutingPolicy "US Only" -OnlinePstnUsages "US and Canada"

# Unrestricted users: US/Canada FIRST so Redmond special-handling applies,
# then International as the fallthrough.
New-CsOnlineVoiceRoutingPolicy "No Restrictions" -OnlinePstnUsages "US and Canada", "International"

Never edit the global (Org-wide default) voice routing policy unless you intend every voice-enabled user — including Calling Plan and Operator Connect users — to inherit it. Doing so can silently divert their PSTN calls down your SBC. Always assign custom policies to the users who should use Direct Routing.

To reorder usages in an existing policy, replace the whole ordered set:

Set-CsOnlineVoiceRoutingPolicy -Identity "No Restrictions" `
  -OnlinePstnUsages @{Replace="US and Canada","International"}

How the whole selection resolves at call time, step by step:

Step What Teams evaluates Input Output
1 Tenant dial plan normalization Dialed string E.164 number
2 User’s voice routing policy E.164 + policy Ordered usage list
3 First usage whose routes match Usage’s routes vs number Candidate route set
4 Lowest-Priority matching route Route priorities The chosen route
5 Random gateway in the route Route’s gateway list The egress SBC
6 Fall through if no gateway healthy Failover codes/time Next route / usage

Step 5 — Assign number, Enterprise Voice, and the voice routing policy

A user needs three things to place a Direct Routing call: a number, Enterprise Voice enabled, and a voice routing policy. Set-CsPhoneNumberAssignment -PhoneNumberType DirectRouting does the first two at once — assigning a number automatically flips EnterpriseVoiceEnabled to true.

# Assign a Direct Routing DID; EnterpriseVoiceEnabled becomes True automatically
Set-CsPhoneNumberAssignment `
  -Identity user1@contoso.com `
  -PhoneNumber "+14255550123" `
  -PhoneNumberType DirectRouting

# Assign the voice routing policy
Grant-CsOnlineVoiceRoutingPolicy -Identity user1@contoso.com -PolicyName "US Only"

If you need to enable Enterprise Voice without (yet) assigning a number — for example a resource account — use the attribute parameter set, which is mutually exclusive with -PhoneNumber:

Set-CsPhoneNumberAssignment -Identity cafe-aa@contoso.com -EnterpriseVoiceEnabled $true

Direct Routing numbers support extensions natively, which is how you map a single trunk DID to a PBX-style range:

Set-CsPhoneNumberAssignment -Identity user2@contoso.com `
  -PhoneNumber "+14255550100;ext=1234" -PhoneNumberType DirectRouting

The PhoneNumberType values and what each implies:

PhoneNumberType Trunk EnterpriseVoiceEnabled side-effect Notes
DirectRouting Your SBC Set to $true on number assignment The subject of this article
CallingPlan Microsoft Set to $true Number must be acquired from Microsoft first
OperatorConnect Partner carrier Set to $true Number provisioned by the operator
(attribute set, no number) Set explicitly via -EnterpriseVoiceEnabled For resource accounts / staged enablement

Step 6 — Tenant dial plans: normalization rules

A dial plan converts what a human dials (a 5-digit extension, a 7-digit local number, a 9 for an outside line) into E.164 so a voice route can match it. Normalization rules are ordered Pattern/Translation pairs; Teams walks them top-down and stops at the first match. Build them in memory, then attach to a dial plan.

# 5-digit internal extension dialing -> full E.164
$ext = New-CsVoiceNormalizationRule -Parent Global `
  -Description "5-digit internal extension" `
  -Pattern '^(\d{5})$' -Translation '+1425555$1' `
  -Name "Internal5" -IsInternalExtension $true -InMemory

# Local 7-digit dialing in the 425 area -> E.164
$local = New-CsVoiceNormalizationRule -Parent Global `
  -Description "Local 7-digit" `
  -Pattern '^(\d{7})$' -Translation '+1425$1' `
  -Name "Local7" -InMemory

# National: a leading 9 then 1+10 digits -> strip the 9, add +
$national = New-CsVoiceNormalizationRule -Parent Global `
  -Description "Outside line + national" `
  -Pattern '^9(1\d{10})$' -Translation '+$1' `
  -Name "Outside9National" -InMemory

New-CsTenantDialPlan -Identity "Seattle-HQ" `
  -Description "Seattle HQ dial plan" `
  -SimpleName "Seattle-HQ" `
  -NormalizationRules @($ext, $local, $national)

Grant-CsTenantDialPlan -Identity user1@contoso.com -PolicyName "Seattle-HQ"

The dial-plan mechanics you have to keep straight:

Concept Rule Consequence if violated
Effective dial plan Tenant dial plan merges with the service-country dial plan Unexpected extra rules apply from the base
Rule order Top-down, first match stops A broad rule above a specific one swallows it
Output format Always emit a leading + (E.164) Non-+ output triggers a second normalization pass
Extension ;ext= preserved through routing, stripped for match Route regex must match the bare E.164
Internal flag -IsInternalExtension marks intra-org dialing Mislabeling affects reporting/handling

Two habits. Always normalize to a leading +. A rule that emits a number without + triggers a second normalization pass against tenant/regional rules — double normalization with surprising results. If the carrier needs + removed on the wire, do it at the trunk with OutboundPstnNumberTranslationRules on the gateway, not in the dial plan. Order restrictive rules above permissive ones — a .* rule before ^(\d{5})$ would swallow extensions.

Test the effective plan against real dialed strings before you let a user near it:

Test-CsEffectiveTenantDialPlan -DialedNumber 42555 -Identity user1@contoso.com
Test-CsEffectiveTenantDialPlan -DialedNumber 5551234 -Identity user1@contoso.com
Test-CsEffectiveTenantDialPlan -DialedNumber 914255550123 -Identity user1@contoso.com

Step 7 — Media bypass and local media optimization

By default, media between the Teams client and the SBC traverses Microsoft’s media processors in the cloud — signaling and media both go up to Microsoft and back down. Media bypass removes the media hop: RTP/SRTP flows directly between the Teams client and the SBC while signaling still goes through the Microsoft SIP proxy. Local media optimization extends this to multi-site topologies: signaling anchors on a central proxy SBC, but media is kept on a downstream SBC local to the user’s network site.

Enable media bypass on the gateway and in the media-bypass configuration (both the SBC and the platform must agree):

# Turn media bypass on for the gateway
Set-CsOnlinePSTNGateway -Identity sbc01.sip.contoso.com -MediaBypass $true

# Enable tenant media-bypass configuration
Set-CsTeamsMediaConnectivityPolicy -Identity Global -DirectConnection Enabled

Media bypass has real prerequisites — it is not a free toggle:

Prerequisite Requirement Why
SBC support The SBC vendor/firmware must support Teams media bypass Direct SRTP negotiation with the client
Same media path Client and SBC must be able to reach each other’s media IPs RTP goes direct; no cloud relay to bridge
ICE / relay Media candidates negotiated via ICE; a relay is a fallback NAT traversal for the direct path
Public IP visibility SBC media reachable from client networks (or via relay) Otherwise the direct path fails and one-way audio results
Gateway flag -MediaBypass $true on the PSTN gateway Platform must know to attempt bypass

When each media mode is right:

Mode Media path Use when Cost
Default (no bypass) Client ↔ Microsoft media processor ↔ SBC Simple topology; SBC not on the client network path Extra hop, added latency, cloud egress
Media bypass Client ↔ SBC (direct SRTP) SBC reachable from client networks; latency/compliance matters Direct path must be reachable; harder to trace
Local media optimization Media stays on a local downstream SBC; signaling central Many sites behind one central proxy SBC Most complex; needs network sites + topology

Local media optimization requires network topology to be defined — network sites, subnets, and the gateway-to-site binding — plus -BypassMode and -GatewaySiteId on the gateways:

# Bind a downstream SBC to a branch site and keep media local
Set-CsOnlinePSTNGateway -Identity sbc-branch01.sip.contoso.com `
  -GatewaySiteId "Branch-London" -BypassMode "OnlyForLocalUsers"

BypassMode = OnlyForLocalUsers keeps bypass to users at the same site as the gateway; Always attempts bypass regardless of site. LMO’s payoff is a media path that never leaves the branch even when the signaling SBC is central — critical for latency-sensitive contact-center audio and for keeping media on-net for compliance.

Step 8 — Emergency calling and E911 routing

Emergency calling earns its operational scrutiny. Two policy types do different jobs: emergency call routing decides which numbers are emergency numbers and which trunk they take; emergency calling decides who gets notified. For dynamic location, you wire the Location Information Service (LIS) to network identifiers. In the US, Kari’s Law (direct 911 dialing, no prefix; on-site notification) and RAY BAUM’S Act (a dispatchable location — building, floor, room — must reach the dispatcher) make this a compliance requirement, not a nicety.

Create an emergency number object and bind it to a routing policy. The dial mask lets users dial a test string (933) that the system treats as 911. The OnlinePSTNUsage must already exist and point at the route to your emergency-capable trunk.

# Emergency number: dial 911 (or 933 as a test mask) out the USE911 usage
$en = New-CsTeamsEmergencyNumber -EmergencyDialString "911" `
  -EmergencyDialMask "933" -OnlinePSTNUsage "USE911"

New-CsTeamsEmergencyCallRoutingPolicy -Identity "US-E911" `
  -EmergencyNumbers @{add=$en} `
  -AllowEnhancedEmergencyServices:$true `
  -Description "US dynamic E911"

Grant-CsTeamsEmergencyCallRoutingPolicy -Identity user1@contoso.com -PolicyName "US-E911"

The emergency calling policy (distinct from routing) controls notification — who is told when someone dials 911, and whether external routing to a security desk happens:

New-CsTeamsEmergencyCallingPolicy -Identity "HQ-Notify" `
  -NotificationMode NotificationOnly `
  -NotificationGroup "security@contoso.com" `
  -Description "Notify HQ security on any 911"

Grant-CsTeamsEmergencyCallingPolicy -Identity user1@contoso.com -PolicyName "HQ-Notify"

The two emergency policies compared — they are not interchangeable:

Aspect Emergency call routing policy Emergency calling policy
Cmdlet *-CsTeamsEmergencyCallRoutingPolicy *-CsTeamsEmergencyCallingPolicy
Decides Which strings are emergency + their PSTN usage Who is notified and how
Key fields EmergencyNumbers, AllowEnhancedEmergencyServices NotificationMode, NotificationGroup, ExternalLocationLookupMode
DR-specific Points 911 at an emergency-capable usage/trunk Notification, conferencing in security
Applies to Direct Routing (routing side) All PSTN options

For dynamic (location-aware) E911, the Teams client reports its network position and LIS returns a civic address. Associate emergency locations with network identifiers — subnet is the most common, but LIS supports several:

# Map an internal subnet to a previously created emergency location
$loc = Get-CsOnlineLisLocation -City Seattle
Set-CsOnlineLisSubnet -Subnet 10.20.30.0 -LocationId $loc.LocationId `
  -Description "Seattle HQ Floor 3 - 10.20.30.0/24"

The LIS network identifiers you can map, from most to least common:

Identifier Cmdlet Granularity Use when
Subnet Set-CsOnlineLisSubnet Per IP subnet The default; matches wired/wireless subnets
Wireless access point (WAP) Set-CsOnlineLisWirelessAccessPoint Per WAP BSSID Floor/zone precision on Wi-Fi
Switch (chassis) Set-CsOnlineLisSwitch Per switch A whole switch = a location
Switch port Set-CsOnlineLisPort Per port Desk-level precision on wired

Two Direct-Routing-specific requirements. Set -PidfLoSupported $true on the emergency-egress gateway so Teams sends the PIDF-LO XML location payload to the SBC, which relays it to the emergency service provider:

Set-CsOnlinePSTNGateway -Identity sbc01.sip.contoso.com -PidfLoSupported $true

Second, for users who never move you can attach a static location to a DID by passing -LocationId to Set-CsPhoneNumberAssignment. Dynamic LIS lookups override the static value when a network match exists.

The dynamic-E911 pieces that must all line up, or the call reaches the wrong dispatcher:

Piece Cmdlet / setting If missing…
Emergency addresses & locations New-CsOnlineLisCivicAddress / ...LisLocation No civic address to send
Network-ID → location map Set-CsOnlineLisSubnet etc. Location stays static / wrong
Emergency call routing policy New-CsTeamsEmergencyCallRoutingPolicy 911 not recognized / no trunk
Emergency calling policy New-CsTeamsEmergencyCallingPolicy No on-site notification
Gateway PIDF-LO -PidfLoSupported $true Location XML never reaches the SBC/provider
Emergency provider / ELIN Carrier / provider config Address not honored on the PSTN side
Test with the dial mask EmergencyDialMask "933" You never validate before an emergency

Step 9 — Survivability: the Survivable Branch Appliance (SBA)

Teams Phone is cloud-dependent: if a branch loses its WAN link to Microsoft, users at that branch lose calling — unless you deploy a Survivable Branch Appliance. The SBA is a supported application that runs on a certified SBC at the branch; when the connection to the Teams cloud is down, users’ Teams clients register to the local SBA and can still make and receive PSTN calls over the local trunk, place internal calls to others at the same branch, and access basic voicemail-to-PSTN behavior. When connectivity returns, clients re-register to the cloud automatically.

You register the SBA and assign a survivability policy pointing users at it:

# Assign the branch SBA via a Teams survivable branch appliance policy
New-CsTeamsSurvivableBranchAppliancePolicy -Identity "London-SBA" `
  -SurvivableBranchAppliance @{add="sba-london.sip.contoso.com"}

Grant-CsTeamsSurvivableBranchAppliancePolicy -Identity user1@contoso.com `
  -PolicyName "London-SBA"

What survives a WAN outage with an SBA, and what does not:

Capability during outage With SBA Without SBA
Outbound PSTN from the branch Yes (local trunk) No
Inbound PSTN to branch users Yes No
Calls between users at the same branch Yes No
Calls to users at other sites / cloud No (WAN down) No
Presence, chat, meetings No (cloud features) No
Auto-reconnect on WAN restore Automatic N/A

The SBA is a resilience layer, not a full PBX — meetings, chat, presence and cross-site calling all still require the cloud. Its job is to keep the branch dialing 911 and taking customer calls through an ISP outage.

Step 10 — Monitoring: Call Analytics and the Call Quality Dashboard

Two tools answer two different questions. Call Analytics (Teams admin center → Users → a user → Call history) is per-call: it shows one call leg by leg — client, network, media path, egress SBC, codec, jitter, packet loss, round-trip time — and is your first stop for “this one call was bad.” The Call Quality Dashboard (CQD) is the tenant-wide aggregate: it trends media quality across all calls so you can spot a single SBC site degrading over a week, a subnet with chronic packet loss, or a Wi-Fi segment that hurts calls. Call Analytics is the microscope; CQD is the map.

The two tools compared:

Aspect Call Analytics Call Quality Dashboard (CQD)
Scope One call / one user Tenant-wide aggregate
Question it answers “Why was this call bad?” “Which site/subnet/SBC trends bad?”
Latency to data Near-real-time (minutes) Hours (aggregated)
Best for Reactive, single-incident triage Proactive quality management
Key data Per-leg jitter, loss, RTT, codec, path Poor-call %, by building/subnet/endpoint
Setup On by default Upload a building/subnet map for full value

The media-quality thresholds Teams uses to classify a call as “poor” — know these so a metric reading is actionable:

Metric Good target “Poor” threshold (indicative) Typical cause when exceeded
Packet loss < 1% > 10% (or ~5% average) Congestion, Wi-Fi, bad uplink
Jitter < 30 ms > 30 ms average Buffering, variable path, QoS missing
Round-trip time < 100 ms > 500 ms Distance, no media bypass, routing detours
Ratio concealed samples low elevated Loss/jitter forcing audio concealment

CQD’s value multiplies when you upload a building/subnet map so poor calls attribute to a named site rather than a raw subnet. Feed CQD your subnet-to-building CSV and the reports become “the London 3rd-floor Wi-Fi has 8% poor calls” instead of “10.20.30.0/24 has 8% poor calls.”

Architecture at a glance

Picture the call as it flows, left to right, because the platform evaluates it in exactly this order. A user dials a string — say 9-1-425-555-0123. It first meets the user’s tenant dial plan, whose ordered normalization rules strip the 9 and produce the E.164 number +14255550123. That number is handed to the user’s online voice routing policy, an ordered list of PSTN usages; Teams walks the list top-to-bottom and stops at the first usage whose voice routes contain a matching NumberPattern. Within that usage it picks the lowest-Priority matching route, and within the route it selects one of the listed PSTN gateways (SBCs) at random. Signaling then travels over TLS on port 5061 from Microsoft’s SIP proxy (sip.pstnhub.microsoft.com, tried first) to the chosen SBC, which relays the SIP INVITE onto the carrier trunk and out to the PSTN.

Media takes one of three routes depending on your configuration — the part worth visualizing separately from signaling. In the default case, RTP/SRTP goes from the client up to a Microsoft media processor and back down to the SBC; signaling and media both transit the cloud. With media bypass, signaling still goes through the Microsoft proxy but media flows directly between the client and the SBC. With local media optimization, a central proxy SBC anchors signaling while a downstream SBC local to the branch keeps media on-net. Overlaid on this path are two life-safety/resilience elements: an emergency call (911, matched by the emergency call routing policy) is steered to an emergency-capable usage and trunk with a PIDF-LO payload derived from LIS matching; and if the branch WAN fails, the client re-registers to a local Survivable Branch Appliance so basic PSTN calling continues. Two instruments watch it all: Call Analytics for a single call’s leg-by-leg detail, and the Call Quality Dashboard for the tenant-wide trend. The model to hold: dialing, routing and signaling always follow the fixed chain — dial plan → usage → route → gateway → carrier — while media is the flexible part you place with bypass and LMO, and emergency and survivability are cross-cutting overlays on the same trunk.

Real-world scenario

A multinational with 9,000 seats consolidated six regional PBXs onto Teams Phone. North America moved to Calling Plans cleanly, but their EMEA contact center sat behind a Genesys platform reachable only over an existing SIP trunk, and the carrier contract had three years left. They chose Direct Routing for EMEA and hit a sharp constraint: contact-center agents must reach internal Genesys queues and the external PSTN, but compliance required that all PSTN calls keep the trunk inside the corporate network — no traffic could traverse the public internet to a Microsoft media processor and back.

Two design decisions solved it. First, they kept Calling Plan and Direct Routing on the same agent users: a voice routing policy whose most-specific usage matched the internal Genesys number ranges routed those calls down the SBC (staying on-net), while everything else fell through to the Calling Plan. Because the SBC pattern was evaluated first and the Calling Plan applies as the implicit last route, no extra “international” route was needed for agents. Second, to keep PSTN media on-net, they enabled media bypass so RTP flowed directly between the Teams client and the SBC rather than through Microsoft’s media processors — and for the London and Frankfurt branches, they layered local media optimization so media stayed on the branch’s downstream SBC even though signaling anchored on a central proxy SBC in the primary data center.

The ordering of usages was the make-or-break detail. Putting the Genesys usage first guaranteed on-net handling for queue calls:

# Genesys internal ranges FIRST -> on-net via SBC; PSTN falls through to Calling Plan
New-CsOnlineVoiceRoute -Identity "Genesys-Queues" `
  -NumberPattern "^\+44(20)(7\d{6})$" `
  -OnlinePstnGatewayList sbc-emea01.sip.contoso.com `
  -Priority 1 -OnlinePstnUsages "EMEA-Internal"

New-CsOnlineVoiceRoutingPolicy "EMEA-Agents" -OnlinePstnUsages "EMEA-Internal"

Then the incident that proved the design. Three weeks after go-live, the Frankfurt site reported that outbound calls to German mobiles (+49 15x…) were failing with a fast-busy about 20% of the time, always at peak. The on-call reflex — “the SBC is down” — was wrong: the SBC health dashboard was green. Call Analytics on a failed call showed the SIP ladder reaching the SBC and the SBC returning a 503 Service Unavailable back toward Microsoft. The 503 was the carrier trunk saturating: the German mobile route pointed at a single SBC whose MaxConcurrentSessions had been set to 150 to match a trunk that the carrier had, unknown to the Teams team, downgraded to 100 channels. Under peak the trunk hit its real ceiling, the carrier 503’d, and because the backup route was authored at a higher Priority number but pointed at the same saturated SBC, failover did nothing.

The fix was two-part. Immediately: correct MaxConcurrentSessions to the real 100 so alerting fired at 90 channels before users felt it, and re-point the backup route (Priority 2) at a genuinely independent SBC on a second carrier trunk so failover had somewhere to go. Structurally: split the German mobile pattern into its own PSTN usage so its capacity was reasoned about separately, and add a CQD building-map entry for Frankfurt so the next capacity problem showed as a site-level trend a week early. The runbook line: “A green SBC and a failing call means read the SIP ladder — the 503 is the trunk, not the box. And a backup route that points at the same SBC is not a backup.” With mixed connectivity, voice routing policy order — and route priority — is the control plane; a transposed usage or a mis-numbered priority breaks the compliance boundary or the failover, silently.

Advantages and disadvantages

Direct Routing’s whole value is control, and control cuts both ways. Weigh it honestly:

Advantages (why you choose it) Disadvantages (why it costs you)
You own the trunk — reuse an existing carrier contract and its economics You operate the SBC, certificates, firewall and routing — real telco ops
Reaches anything: analog paging, PBX, contact center, any SIP carrier Every layer is yours to get right; failures are quiet, not loud
Works in countries with no Calling Plan / Operator Connect coverage Setup is the slowest of the three options (build + certify + validate)
Full routing control: dial plans, normalization, per-pattern routing Misordered usages / mis-numbered priorities misroute calls silently
Media bypass and LMO keep media on-net for latency and compliance Media bypass complicates troubleshooting (fewer central logs)
Survivability via SBA keeps branches calling through a WAN outage SBA is extra hardware/config and only covers basic calling
Coexists with Calling Plans/Operator Connect on the same user Certificate expiry silently kills the whole trunk if unmonitored

Direct Routing is right when a managed option cannot reach what you need — a contact center, an incumbent carrier contract, an uncovered country, or a compliance rule about media locality. It is the wrong default when Microsoft or an operator sells numbers where you operate and your call patterns are simple: then Calling Plans or Operator Connect give you 90% of the value with a fraction of the operational surface. The disadvantages are all manageable — but only if you know they exist and monitor for them, which is the point of this article.

Hands-on lab

You cannot pair a real SBC without hardware and a public certificate, so this lab builds and validates the entire routing plane against your tenant using PowerShell — the part that most often hides bugs — then tears it down cleanly. It creates a lab PSTN gateway object (an FQDN under a domain you control), the usages/routes/policies on top of it, and a dial plan, and validates each with the built-in test cmdlets. Run in a PowerShell session connected with Connect-MicrosoftTeams as a Teams Administrator.

Step 1 — Connect and confirm your domains. The gateway FQDN must sit under a verified tenant domain.

Connect-MicrosoftTeams
(Get-CsTenant).Domains

Expected: a list of your verified domains. Pick one (e.g. sip.contoso.com) for the lab FQDN.

Step 2 — Create a lab PSTN gateway (kept disabled). Creating it disabled means it never takes real calls — safe for a lab.

New-CsOnlinePSTNGateway -Fqdn lab-sbc.sip.contoso.com `
  -SipSignalingPort 5061 -Enabled $false `
  -MaxConcurrentSessions 50 -Description "LAB - do not enable"
Get-CsOnlinePSTNGateway -Identity lab-sbc.sip.contoso.com |
  Format-List Identity, Enabled, SipSignalingPort, MaxConcurrentSessions

Expected: the gateway listed with Enabled : False.

Step 3 — Create PSTN usages.

Set-CsOnlinePstnUsage -Identity Global -Usage @{Add="LAB-USCA"}
Set-CsOnlinePstnUsage -Identity Global -Usage @{Add="LAB-Intl"}
(Get-CsOnlinePstnUsage).Usage | Where-Object { $_ -like "LAB-*" }

Expected: LAB-USCA and LAB-Intl appear in the list.

Step 4 — Create voice routes (active + catch-all + international).

New-CsOnlineVoiceRoute -Identity "LAB-Redmond" `
  -NumberPattern "^\+1(425|206)(\d{7})$" `
  -OnlinePstnGatewayList lab-sbc.sip.contoso.com `
  -Priority 1 -OnlinePstnUsages "LAB-USCA"

New-CsOnlineVoiceRoute -Identity "LAB-OtherUS" `
  -NumberPattern "^\+1(\d{10})$" `
  -OnlinePstnGatewayList lab-sbc.sip.contoso.com `
  -Priority 2 -OnlinePstnUsages "LAB-USCA"

New-CsOnlineVoiceRoute -Identity "LAB-Intl" `
  -NumberPattern ".*" `
  -OnlinePstnGatewayList lab-sbc.sip.contoso.com `
  -Priority 3 -OnlinePstnUsages "LAB-Intl"

Get-CsOnlineVoiceRoute | Where-Object Identity -like "LAB-*" |
  Sort-Object Priority | Format-Table Identity, Priority, NumberPattern, OnlinePstnUsages

Expected: three routes, priorities 1–3, in order.

Step 5 — Create the voice routing policy (usage order matters).

New-CsOnlineVoiceRoutingPolicy "LAB-NoRestrictions" `
  -OnlinePstnUsages "LAB-USCA", "LAB-Intl"
Get-CsOnlineVoiceRoutingPolicy -Identity "LAB-NoRestrictions" |
  Format-List Identity, OnlinePstnUsages

Expected: the policy with LAB-USCA listed before LAB-Intl.

Step 6 — Create and test a dial plan.

$labExt = New-CsVoiceNormalizationRule -Parent Global -Name "LABInternal5" `
  -Pattern '^(\d{5})$' -Translation '+1425555$1' -IsInternalExtension $true -InMemory
$labNat = New-CsVoiceNormalizationRule -Parent Global -Name "LABOutside9" `
  -Pattern '^9(1\d{10})$' -Translation '+$1' -InMemory

New-CsTenantDialPlan -Identity "LAB-DialPlan" -SimpleName "LAB-DialPlan" `
  -NormalizationRules @($labExt, $labNat)

# Validate normalization against the tenant dial plan (no user needed for the tenant scope)
Test-CsEffectiveTenantDialPlan -DialedNumber 55123 -TenantDialPlan "LAB-DialPlan"
Test-CsEffectiveTenantDialPlan -DialedNumber 914255550123 -TenantDialPlan "LAB-DialPlan"

Expected: 55123 normalizes to +1425555512... shape (per your translation) and 914255550123 normalizes to +14255550123, both matched by the expected rule.

Step 7 — (Optional) assign to a test user and verify end-to-end config. Only if you have a spare, licensed, non-production test user.

$u = "labtest@contoso.com"
Grant-CsOnlineVoiceRoutingPolicy -Identity $u -PolicyName "LAB-NoRestrictions"
Grant-CsTenantDialPlan -Identity $u -PolicyName "LAB-DialPlan"
Get-CsOnlineUser $u |
  Format-List UserPrincipalName, EnterpriseVoiceEnabled, OnlineVoiceRoutingPolicy, TenantDialPlan

Expected: the user shows OnlineVoiceRoutingPolicy : LAB-NoRestrictions and TenantDialPlan : LAB-DialPlan.

Validation checklist. You built the full routing plane — gateway, usages, routes, policy, dial plan — and validated normalization with Test-CsEffectiveTenantDialPlan without placing a real call or enabling a live trunk. The lab steps mapped to what each proves:

Step What you did What it proves
2 Create a disabled gateway Pairing object exists; safe (no calls)
3 Add PSTN usages The containers routes/policies reference
4 Add prioritized routes Regex matching and priority ordering work
5 Create the ordered policy Usage order is captured as authored
6 Create + test the dial plan Normalization produces the E.164 you expect
7 Assign to a test user The user picks up the policy and dial plan

Teardown (remove every lab object).

# Unassign from the test user first if you did Step 7
Grant-CsOnlineVoiceRoutingPolicy -Identity $u -PolicyName $null
Grant-CsTenantDialPlan -Identity $u -PolicyName $null

Remove-CsTenantDialPlan -Identity "LAB-DialPlan"
Remove-CsOnlineVoiceRoutingPolicy -Identity "LAB-NoRestrictions"
Get-CsOnlineVoiceRoute | Where-Object Identity -like "LAB-*" |
  ForEach-Object { Remove-CsOnlineVoiceRoute -Identity $_.Identity }
Set-CsOnlinePstnUsage -Identity Global -Usage @{Remove="LAB-USCA"}
Set-CsOnlinePstnUsage -Identity Global -Usage @{Remove="LAB-Intl"}
Remove-CsOnlinePSTNGateway -Identity lab-sbc.sip.contoso.com

Cost note. This lab creates only configuration objects in your tenant — there is no per-hour Azure charge and no PSTN usage. The only prerequisite cost is the Teams Phone license already assigned to any test user in Step 7.

Common mistakes & troubleshooting

Telephony fails quietly, so the playbook is about reading the right signal. First as a scannable table you keep open mid-incident, then the detail on the entries that bite hardest.

# Symptom Root cause Confirm (exact cmd / portal path) Fix
1 SBC never healthy; no call completes Cert chain not trusted / missing intermediate / wrong CN SBC health dashboard (blank); SBC TLS logs; verify chain Full public-CA chain, CN/SAN = FQDN, CA in MS Trusted Root
2 Gateway paired but no calls route Gateway created without -Enabled $true Get-CsOnlinePSTNGateway | fl Enabled → False Set-CsOnlinePSTNGateway -Enabled $true
3 No SIP OPTIONS on the dashboard Firewall blocking 5061 inbound, or SendSipOptions $false Dashboard OPTIONS state; firewall rules; gateway flag Open TCP 5061 from 52.112/14+52.120/14; SendSipOptions $true
4 Outbound call fails, fast-busy No matching voice route / bad NumberPattern regex Get-CsOnlineVoiceRoute; test the regex on the E.164 Fix/anchor the pattern; add a catch-all route
5 Calls take the wrong trunk Usage order in the policy (first match wins) Get-CsOnlineVoiceRoutingPolicy | fl OnlinePstnUsages Reorder usages most-specific-first
6 Failover doesn’t happen when SBC saturates Backup route higher Priority number but same SBC, or wrong failover codes Get-CsOnlineVoiceRoute | sort Priority; gateway FailoverResponseCodes Point backup at an independent SBC; add carrier 503 to failover codes
7 Call connects, but one-way / no audio Media ports / NAT on the SBC; bypass path unreachable Call Analytics per-leg media; SBC media logs Open media ranges; fix NAT/SBC public media IP; verify bypass reachability
8 Call rejected with 488 Not Acceptable Here Codec mismatch between Teams and SBC/carrier SIP ladder in Call Analytics / SBC logs Enable a Teams-supported codec (e.g. G.722, OPUS) on the SBC
9 User has no dial pad Not voice-enabled / no routing policy / no license Get-CsOnlineUser | fl EnterpriseVoiceEnabled, OnlineVoiceRoutingPolicy Assign number (sets EV true) + voice routing policy + license
10 911 reaches the wrong dispatcher LIS not mapped / no dynamic location / no PIDF-LO Get-CsOnlineLisSubnet; gateway PidfLoSupported Map subnet→location; -PidfLoSupported $true; test via 933 mask
11 Inbound calls fail; outbound fine Carrier not sending to the right SBC / number not assigned Carrier logs; Get-CsPhoneNumberAssignment Fix carrier routing to SBC; assign the DID to the user
12 Trunk dead tenant-wide overnight Certificate expired SBC cert validity; dashboard TLS down Renew cert; add expiry monitoring/alerting
13 Some calls one-way after enabling media bypass SBC media IP not reachable from client subnet Call Analytics media path; bypass reachability test Make SBC media reachable (or relay); or disable bypass for that site
14 Emergency test (933) does nothing Emergency call routing policy not assigned / wrong usage Get-CsTeamsEmergencyCallRoutingPolicy; user assignment Assign policy; point emergency number at an emergency-capable usage

The expanded form for the entries that cause the most wasted hours:

1. SBC never healthy; no call ever completes. Root cause: the certificate chain is not trusted by Microsoft — a private/self-signed cert, a missing intermediate, an expired leaf, or a CN/SAN that doesn’t match the FQDN. Confirm: the SBC health dashboard (Voice → Direct Routing) shows the SBC blank or TLS-down; the SBC’s own TLS logs show the handshake failing; verify the leaf’s full chain roots in the Microsoft Trusted Root Certificate Program. Fix: install the full public-CA chain (leaf + all intermediates) on the SBC, with CN or SAN equal to the exact FQDN and EKU Server Authentication; re-test OPTIONS.

2. Gateway paired but nothing routes. Root cause: New-CsOnlinePSTNGateway creates the gateway disabled by default and someone forgot -Enabled $true. Confirm: Get-CsOnlinePSTNGateway | Format-List Identity, Enabled shows Enabled : False. Fix: Set-CsOnlinePSTNGateway -Identity <fqdn> -Enabled $true.

4. Outbound call fails with fast-busy; SBC is healthy. Root cause: no voice route matches the E.164 number — an over-specific or mis-anchored NumberPattern, or no catch-all — so the call has nowhere to go. Confirm: Get-CsOnlineVoiceRoute | Sort-Object Priority | Format-Table Identity, Priority, NumberPattern, OnlinePstnUsages; test the regex against the real E.164 string. Fix: correct/anchor the pattern (test it first), and ensure a lower-priority catch-all exists for the intended scope.

5. Calls take the wrong trunk. Root cause: usage order in the voice routing policy — evaluation is first-match-wins, so a broad usage placed above a specific one wins. Confirm: Get-CsOnlineVoiceRoutingPolicy -Identity <name> | Format-List OnlinePstnUsages and read the order. Fix: Set-CsOnlineVoiceRoutingPolicy -OnlinePstnUsages @{Replace=<most-specific-first order>}.

7. Call connects but audio is one-way or absent. Root cause: media (RTP/SRTP) is blocked or NAT’d — the media-processor ranges are closed (non-bypass), or with bypass the SBC’s media IP isn’t reachable from the client subnet. Confirm: Call Analytics for the call shows the media leg with loss/no packets on one direction; the SBC media logs corroborate. Fix: open the media ranges (3478–3481, 49152–53247) for non-bypass, fix SBC NAT/public media IP, or verify bypass reachability; disable bypass for the affected site as a fallback.

8. Call rejected with SIP 488. Root cause: the SBC/carrier and Teams share no offered codec — a 488 Not Acceptable Here is a codec negotiation failure. Confirm: the SIP ladder in Call Analytics or SBC logs shows the INVITE answered with 488. Fix: enable a Teams-supported codec on the SBC (Teams uses SILK/OPUS toward the client and commonly G.711/G.722 toward the trunk); align the SBC’s codec list.

10. A 911 call reaches the wrong dispatcher. Root cause: dynamic location isn’t working — the caller’s subnet/WAP/switch isn’t mapped in LIS, or the egress gateway lacks -PidfLoSupported $true, so a static or default location is used. Confirm: Get-CsOnlineLisSubnet for the caller’s subnet; Get-CsOnlinePSTNGateway | fl PidfLoSupported. Fix: map the network identifier to a valid civic location, set -PidfLoSupported $true on the emergency-egress gateway, and validate with the 933 dial mask before relying on it.

The SIP response codes you will actually see on this path, and what each usually means here:

SIP code Meaning Typical cause on Direct Routing Where it comes from
401 / 407 Unauthorized / Proxy auth Trunk registration/auth to the carrier Carrier
403 Forbidden Carrier rejects the number/CLID Carrier
404 Not Found Number not provisioned on the trunk Carrier
408 Request Timeout SBC unreachable / no answer in time Network / SBC
480 Temporarily Unavailable User offline / no survivable route Teams / SBA
486 Busy Here Called party busy PSTN
488 Not Acceptable Here Codec mismatch SBC / carrier
500 Server Internal Error SBC or cloud-side fault SBC / Microsoft
503 Service Unavailable Trunk saturated / SBC overloaded SBC / carrier
504 Server Time-out Downstream (carrier) timeout Carrier

Best practices

Security notes

Cost & sizing

Direct Routing shifts cost from per-user Microsoft calling to your carrier and your own infrastructure — the trade you make for control.

A rough monthly picture for a 500-seat Direct Routing deployment:

Cost driver What you pay for Rough monthly (INR) Sizing lever
Certified SBC (redundant pair) Appliance/VM + support (amortized) ₹15,000–40,000 One pair per data center; scale by concurrency
Public-CA certificate Annual cert (amortized) ₹500–1,700 Watch expiry; wildcard vs SAN
Carrier SIP trunk Channels + minutes ₹20,000–80,000 Channels = busy-hour concurrency + headroom
Teams Phone licenses Per voice-enabled user Per your EA/CSP Only license users who need PSTN
Resource-account licenses Call queues / auto attendants Often ₹0 (allocation) Use the free virtual-user pool
Branch SBA (per site) SBC at the branch ₹10,000–25,000/site Only sites needing survivability
Network / QoS WAN + egress capacity Existing budget Bypass reduces media egress

The sizing discipline that matters most: channels, not seats. MaxConcurrentSessions on each gateway should equal the carrier’s real channel count so the 90% alert is meaningful; over-provisioning channels wastes carrier spend, under-provisioning produces exactly the 503-under-peak failure in the scenario above.

Interview & exam questions

1. When do you choose Direct Routing over Calling Plans or Operator Connect? When a managed option cannot reach what you need: Microsoft has no Calling Plan in your country, you have an in-force carrier/SIP-trunk contract, or you must integrate analog/PBX/contact-center gear. Direct Routing gives full routing control at the cost of operating the SBC and trunk yourself; the three options can also coexist on one tenant or user.

2. Walk the full outbound call-selection order for a Direct Routing user. The dialed string is normalized to E.164 by the user’s tenant dial plan; the E.164 number is evaluated against the user’s voice routing policy (ordered PSTN usages); Teams stops at the first usage whose voice routes contain a matching NumberPattern; within that usage it picks the lowest-Priority matching route; within the route it selects a PSTN gateway at random; signaling goes over TLS 5061 via the Microsoft SIP proxy to the SBC and onto the carrier.

3. What are the certificate requirements for pairing an SBC, and what breaks if they’re wrong? The cert’s CN or SAN must equal the SBC FQDN, the issuing CA must be in the Microsoft Trusted Root Certificate Program, the full chain (leaf + intermediates) must be installed, EKU must include Server Authentication, and the FQDN’s domain must be verified in the tenant. A wrong or self-signed cert, a missing intermediate, or a wildcard that doesn’t cover the label fails the TLS handshake — the SBC never shows healthy and no call completes, with no obvious error.

4. Why is PSTN-usage order the “control plane,” and what’s the classic bug? A voice routing policy is an ordered list of usages evaluated first-match-wins; the order decides which route is even considered. The classic bug is placing a broad usage above a specific one (or a .* route inside an early usage), which silently routes calls down the wrong trunk — e.g. sending internal contact-center queue calls out to the PSTN and back.

5. A user reports a fast-busy on outbound calls but the SBC health dashboard is green. Where do you look? Read the SIP ladder in Call Analytics: a green SBC with a failing call usually means a downstream response — a 503 (trunk saturated) or 403/404 (carrier rejecting the number/CLID). Check MaxConcurrentSessions versus real trunk capacity and whether the backup route points at an independent SBC; a green box does not mean a healthy trunk.

6. What’s the difference between an emergency call routing policy and an emergency calling policy? The routing policy (*-CsTeamsEmergencyCallRoutingPolicy) defines which strings are emergency numbers and which PSTN usage/trunk they take — the Direct-Routing-specific side. The calling policy (*-CsTeamsEmergencyCallingPolicy) defines notification: who is alerted (e.g. security), how, and external-lookup behavior. Both, plus LIS and PidfLoSupported, are needed for compliant dynamic E911.

7. How does dynamic E911 determine the caller’s location on Direct Routing, and what’s the DR-specific requirement? The Teams client reports its network position (subnet, WAP, switch/port); the Location Information Service maps that identifier to a civic address; the address travels as a PIDF-LO XML payload. Direct Routing additionally requires -PidfLoSupported $true on the emergency-egress gateway so the SBC receives and relays that location to the emergency provider.

8. What does media bypass change, and what must be true for it to work? It removes the Microsoft media processor from the path so RTP/SRTP flows directly between the Teams client and the SBC (signaling still transits the Microsoft proxy). It requires an SBC that supports Teams media bypass, media reachability between the client networks and the SBC (via ICE/relay for NAT), and -MediaBypass $true on the gateway; if the direct path isn’t reachable you get one-way audio.

9. What survives a branch WAN outage with a Survivable Branch Appliance, and what doesn’t? With an SBA, users at the branch keep outbound and inbound PSTN calls over the local trunk and calls between users at the same branch (including 911). They lose cross-site/cloud calls, presence, chat and meetings — those need the cloud. Clients auto-reconnect to the cloud when the WAN recovers.

10. Call Analytics versus the Call Quality Dashboard — when do you use each? Call Analytics is per-call/per-user: use it reactively to diagnose why one call was bad, leg by leg (jitter, loss, RTT, codec, media path, egress SBC). CQD is the tenant-wide aggregate: use it proactively to spot a site/subnet/SBC trending poor over time; upload a building/subnet map so poor calls attribute to named locations.

11. A brand-new voice-enabled user has no dial pad. What do you check? That the user is licensed for Teams Phone, has a number assigned (which sets EnterpriseVoiceEnabled = True), and has a voice routing policy assigned — Get-CsOnlineUser | fl EnterpriseVoiceEnabled, OnlineVoiceRoutingPolicy, LineUri. All three are required; missing any one removes calling.

12. Why must every dial-plan normalization rule emit a leading +? Output without a + isn’t recognized as complete E.164 and triggers a second normalization pass against tenant/regional rules, producing surprising results. Keep the dial plan producing E.164 and push any carrier-required digit stripping to the trunk via OutboundPstnNumberTranslationRules on the gateway.

These map primarily to the Microsoft 365 Certified: Teams Voice Engineer Expert (MS-720) exam — plan and configure Teams Phone, Direct Routing, dial plans and voice routing, emergency calling, and monitoring — and touch MS-700 (Teams Administrator Associate) for the tenant-admin surface.

Question theme Primary cert Objective area
PSTN option choice; DR vs Calling Plan vs OC MS-720 Plan Teams Phone
SBC pairing, certificates, gateways MS-720 Configure Direct Routing
Dial plans, normalization, voice routing MS-720 Configure voice routing
Dynamic E911, LIS, emergency policies MS-720 Configure emergency calling
Media bypass, LMO, survivability (SBA) MS-720 Optimize/secure network for voice
Call Analytics, CQD, quality management MS-720 Monitor and troubleshoot
Tenant admin, roles, policy assignment MS-700 Manage Teams and policies

Quick check

  1. In what order does Teams evaluate a Direct Routing outbound call — from the dialed string to the egress SBC?
  2. A newly paired SBC never appears healthy and no call completes, but there’s no obvious error. Name the two most likely causes and where you confirm them.
  3. True or false: within a single voice route, the SBCs in OnlinePstnGatewayList are tried in the order listed.
  4. Your contact-center queue calls are going out to the PSTN and back instead of staying on-net. What single configuration detail is almost certainly wrong?
  5. For dynamic E911 over Direct Routing, name the one gateway parameter that is specific to Direct Routing and what it does.

Answers

  1. Tenant dial plan normalizes the dialed string to E.164 → the voice routing policy (ordered PSTN usages) is walked top-to-bottom, first matching usage wins → within that usage the lowest-Priority matching voice route is chosen → within that route a PSTN gateway is selected at random → signaling goes over TLS 5061 via the Microsoft SIP proxy to the SBC and onto the carrier.
  2. Most likely (a) the certificate chain isn’t trusted — self-signed/private CA, missing intermediate, or CN/SAN not matching the FQDN (confirm on the SBC health dashboard and the SBC’s TLS logs); or (b) a firewall blocking TCP 5061 inbound from Microsoft’s subnets so SIP OPTIONS don’t flow (confirm on the dashboard OPTIONS state and firewall rules).
  3. False. SBCs within a single route are tried in random order; use route priority (lowest number wins) to express preference between trunks.
  4. The order of PSTN usages in the voice routing policy. Evaluation is first-match-wins, so the specific “internal/queue” usage must be listed before the broad PSTN usage; a transposed order sends queue calls out to the public network.
  5. -PidfLoSupported $true on the emergency-egress gateway — it makes Teams send the PIDF-LO XML location payload to the SBC, which relays the caller’s civic location to the emergency service provider.

Glossary

Next steps

You can now build, voice-enable, secure, and monitor a Teams Phone Direct Routing deployment end to end. Build outward:

Teams PhoneDirect RoutingSBCVoice RoutingPSTNE911Media BypassPowerShell
Need this built for real?

Vinod is a Senior Cloud Architect (22+ yrs) — available for Azure / AWS / GCP architecture, landing zones, and migrations.

Work with me

Comments

Keep Reading