Building a Simple SBC: Routes, Trunks, and WebRTC

M
Miuda Team
Building conversational AI tooling
Rust Telephony SBC WebRTC SIP

Your team can call each other internally. Now you need to connect to the PSTN through a carrier, let customers call in from web browsers, and manage it all without spinning up a separate SBC appliance.

RustPBX’s SBC addon handles this in a single runtime – no Janus, no FreeSWITCH, no extra media servers.

The Problem

A typical setup needs:

  • Outbound: Employees dial external numbers through a SIP carrier
  • Inbound: Customers call a DID number that rings an extension or queue
  • WebRTC: Browser-based callers connect without installing anything
  • Reliability: Health checks, failover, and capacity controls on each trunk

Traditionally, this means a SIP proxy, an SBC, a media server for WebRTC, and a PBX – all separate boxes. RustPBX collapses this into one process.

Step 1: Enable the SBC Addon

In your config.toml:

[proxy]
addons = ["sbc"]

Restart RustPBX (or reload from the console). The sidebar now shows the SBC section with Dashboard, Trunks, Routes, and Validation.

SBC dashboard

Step 2: Add a SIP Trunk

A trunk represents your connection to a carrier. Go to SBC > Trunks and create one:

FieldExampleNotes
Namecarrier-acmeInternal identifier
DirectionBidirectionalInbound + Outbound
SIP Serversip.carrier.com:5060Carrier signaling address
SIP TransportUDPOr TCP/TLS for secure trunks
Auth Usernamemyaccount123Carrier-provided
Auth Password ********Carrier-provided
RegisterYesRegister with carrier if required
Max Concurrent100Capacity limit
Max CPS10Calls per second throttle

Trunk creation

You can also configure trunks via TOML files for GitOps workflows:

# config/trunks/carrier-acme.toml
dest = "sip.carrier.com:5060"
direction = "bidirectional"
username = "myaccount123"
password = "secret"
transport = "udp"
max_calls = 100
max_cps = 10

SBC-Specific Trunk Settings

On the trunk detail page, expand the SBC section to control:

  • Media Mode: Auto (proxy media when NAT detected), Bypass, Force Transcode
  • Video Policy: PassThrough, Strip, or Transcode
  • Codecs: Whitelist specific codecs (e.g. PCMA, PCMU, Opus)
  • Health Check: Enable periodic probing with automatic failover to a backup trunk
  • CAC Policy: Lossy (allow over-subscription), Reject, or Overflow to a fallback trunk
  • Header Rules: Manipulate SIP headers (add P-Asserted-Identity, strip X-Custom)

Trunk SBC settings

Step 3: Create Routing Rules

Routes decide where calls go. Go to SBC > Routes and create two rules:

Outbound Route

FieldValue
DirectionOutbound
Priority10
Source Pattern1xxx (internal extensions)
Destination Pattern.* (any external number)
StrategyRoundRobin
Target Trunkscarrier-acme

This routes any call from an extension (1001-1999) to an external number through the Acme carrier.

Inbound Route

FieldValue
DirectionInbound
Priority10
Source Trunkcarrier-acme
Destination Pattern18005551234
ActionRoute to Extension 1001

This sends calls to your DID (18005551234) straight to Alice’s extension.

Route editor

Routes support a rich document editor where you can chain matchers, rewrite rules, and actions – all through the console.

Step 4: WebRTC Support

Here’s where it gets simple. RustPBX’s SIP proxy natively handles WebSocket transport for WebRTC clients. No Janus, no mediasoup, no TURN server churn.

Configure the ICE port range in config.toml:

[proxy]
webrtc_port_start = 10000
webrtc_port_end = 20000

WebRTC clients connect via wss://your-server:8080/ws. The proxy handles:

  • WebSocket → SIP translation
  • ICE/DTLS/SRTP negotiation (via RustRTC)
  • Media proxying or bypass
  • Opus codec transcoding if needed

A browser-based customer can now call your DID, and it rings Alice’s desk phone. Same infrastructure, zero extra components.

Step 5: Monitor and Validate

The SBC Dashboard shows:

  • Trunk health status (Healthy / Warning / Standby / Offline)
  • Active call count per trunk
  • System uptime

Use Validation to test your configuration before going live:

  1. Simulate a call: enter a source, destination, and direction
  2. The engine evaluates all routes and shows which rule matches
  3. Verify the selected trunk, rewritten headers, and codec negotiation

Route simulation

Hot-reload trunks and routes from the console without restarting the entire service.

What You Get

With just a trunk and a route, RustPBX functions as a complete SBC:

  • Topology hiding – your internal network stays private
  • DoS protection with rate limiting and IP allowlists
  • Media control – proxy, bypass, or transcode per trunk
  • Header manipulation for carrier interop
  • Health checks with automatic failover

All from a single binary, managed through a web console.

What’s Next?

Routes and trunks handle deterministic routing. But what if you need to call an external API to decide where each call goes? That’s where JSON-RPC routing comes in – perfect for privacy numbers, dynamic routing, and CRM integration. We’ll cover that in the next post.