JSON-RPC Routing
The most powerful feature of SBC is JSON-RPC dynamic routing: each inbound SIP INVITE can call an external HTTP API to determine how the call should be handled.
1. How It Works
SIP INVITE arrives
│
▼
SBC Inspector
│
├── Extract match fields (Caller, Callee, Direction...)
├── Match Rule list
│ │
│ ▼ Rule matched
├── Render request template (Jinja2)
├── HTTP POST to upstream API
│ │
│ ▼ Response received
├── Parse response JSON
├── Apply number rewrite (caller_rewrite, callee_rewrite)
├── Inject/remove Headers
└── Return routing decision (forward / reject / busy)
2. Configuration File
config/sbc/sbc_jsonrpc.toml:
[[rules]]
name = "route-by-api"
description = "Route all inbound calls via API"
[rules.match]
logic = "all"
conditions = [
{ field = "Direction", operator = "Equals", value = "inbound" }
]
[rules.upstream]
url = "http://10.0.0.50:3000/api/sbc/route"
method = "POST"
timeout_ms = 2000
retries = 1
[rules.upstream.headers]
Authorization = "Bearer my-api-key"
Content-Type = "application/json"
[rules.upstream.body_template]
# Jinja2 template
body = '''
{
"caller": "{{ caller }}",
"callee": "{{ callee }}",
"call_id": "{{ call_id }}",
"from_host": "{{ from_host }}",
"direction": "{{ direction }}"
}
'''
[rules.response]
# Extract routing decisions from response JSON
action_field = "action" # "forward" / "reject" / "busy"
trunk_field = "trunk" # Trunk name to forward to
caller_rewrite_field = "caller" # Rewritten caller
callee_rewrite_field = "callee" # Rewritten callee
reject_code_field = "sip_code" # SIP status code for rejection
[[rules.response.header_injections]]
action = "add"
name = "X-Route-Source"
value = "sbc-jsonrpc"
3. Match Conditions
3.1 Matchable Fields
| Field | Description | Example |
|---|---|---|
| Caller | Full caller URI | sip:1000@10.0.0.1 |
| Callee | Full callee URI | sip:9000@10.0.0.1 |
| CallerUser | Caller number | 1000 |
| CalleeUser | Callee number | 9000 |
| CallerHost | Caller domain/IP | 10.0.0.1 |
| CalleeHost | Callee domain/IP | 10.0.0.1 |
| Direction | Direction | inbound / outbound |
| CallId | SIP Call-ID | abc@10.0.0.1 |
| UserAgent | SIP User-Agent | Linphone/4.0 |
| Header | Custom Header | Any Header name |
3.2 Operators
| Operator | Description |
|---|---|
| Equals | Exact match |
| NotEquals | Not equal |
| Contains | Contains |
| StartsWith | Prefix match |
| EndsWith | Suffix match |
| Regex | Regex match |
| Exists | Header exists |
| NotExists | Header does not exist |
3.3 Logic Combination
[rules.match]
logic = "all" # all = all conditions must match, any = any condition matches
conditions = [
{ field = "Direction", operator = "Equals", value = "inbound" },
{ field = "CalleeUser", operator = "StartsWith", value = "9" },
]
4. Upstream API Contract
4.1 Request (SBC → Upstream)
SBC sends an HTTP POST, with the body rendered from the template.
4.2 Response (Upstream → SBC)
{
"action": "forward",
"trunk": "carrier-a",
"callee": "861012345678",
"caller": "057112345678",
"headers": {
"X-Custom": "value"
}
}
action values:
| Value | Description |
|---|---|
forward | Forward to the specified trunk |
reject | Reject (can specify SIP status code) |
busy | Return 486 Busy |
5. Template Variables
Available variables in Jinja2 templates:
| Variable | Description |
|---|---|
{{ caller }} | Caller number |
{{ callee }} | Callee number |
{{ call_id }} | SIP Call-ID |
{{ from_host }} | Source IP |
{{ to_host }} | Destination IP |
{{ direction }} | Direction |
{{ user_agent }} | User-Agent |
6. Console Operations
6.1 JSON-RPC Configuration Management
SBC → Routes → JSON-RPC Config: Edit and save sbc_jsonrpc.toml via the Web UI.
6.2 Rule Simulation
SBC → Routes → Simulate: Enter Caller/Callee and other fields to test which rule matches and preview the request sent to the upstream API.
6.3 Route Simulation
SBC → Routes → Route Simulation: Fully simulate the routing decision process for an INVITE (including JSON-RPC calls).