Building a Simple SBC: Routes, Trunks, and WebRTC
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.

Step 2: Add a SIP Trunk
A trunk represents your connection to a carrier. Go to SBC > Trunks and create one:
| Field | Example | Notes |
|---|---|---|
| Name | carrier-acme | Internal identifier |
| Direction | Bidirectional | Inbound + Outbound |
| SIP Server | sip.carrier.com:5060 | Carrier signaling address |
| SIP Transport | UDP | Or TCP/TLS for secure trunks |
| Auth Username | myaccount123 | Carrier-provided |
| Auth Password | ******** | Carrier-provided |
| Register | Yes | Register with carrier if required |
| Max Concurrent | 100 | Capacity limit |
| Max CPS | 10 | Calls per second throttle |

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, orTranscode - 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, orOverflowto a fallback trunk - Header Rules: Manipulate SIP headers (add
P-Asserted-Identity, stripX-Custom)

Step 3: Create Routing Rules
Routes decide where calls go. Go to SBC > Routes and create two rules:
Outbound Route
| Field | Value |
|---|---|
| Direction | Outbound |
| Priority | 10 |
| Source Pattern | 1xxx (internal extensions) |
| Destination Pattern | .* (any external number) |
| Strategy | RoundRobin |
| Target Trunks | carrier-acme |
This routes any call from an extension (1001-1999) to an external number through the Acme carrier.
Inbound Route
| Field | Value |
|---|---|
| Direction | Inbound |
| Priority | 10 |
| Source Trunk | carrier-acme |
| Destination Pattern | 18005551234 |
| Action | Route to Extension 1001 |
This sends calls to your DID (18005551234) straight to Alice’s extension.

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:
- Simulate a call: enter a source, destination, and direction
- The engine evaluates all routes and shows which rule matches
- Verify the selected trunk, rewritten headers, and codec negotiation

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.