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

FieldDescriptionExample
CallerFull caller URIsip:1000@10.0.0.1
CalleeFull callee URIsip:9000@10.0.0.1
CallerUserCaller number1000
CalleeUserCallee number9000
CallerHostCaller domain/IP10.0.0.1
CalleeHostCallee domain/IP10.0.0.1
DirectionDirectioninbound / outbound
CallIdSIP Call-IDabc@10.0.0.1
UserAgentSIP User-AgentLinphone/4.0
HeaderCustom HeaderAny Header name

3.2 Operators

OperatorDescription
EqualsExact match
NotEqualsNot equal
ContainsContains
StartsWithPrefix match
EndsWithSuffix match
RegexRegex match
ExistsHeader exists
NotExistsHeader 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:

ValueDescription
forwardForward to the specified trunk
rejectReject (can specify SIP status code)
busyReturn 486 Busy

5. Template Variables

Available variables in Jinja2 templates:

VariableDescription
{{ 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).