Routes

Install DotSIP, open the web admin, connect real SIP, and decide each call from your own HTTP API.

DotSIP has no dialplan. Before a call is answered, it POSTs one JSON object to a route URL and runs the JSON that comes back. The same POST happens after an API originate when a route is selected.

Which URL is used:

  • The caller is an extension, dialed is set, and that extension has an outbound route that exists: that outbound route.
  • Otherwise, the SIP channel has an inbound route that exists: that inbound route.
  • Otherwise: route-tools.
  • An API originate can name a route. See HTTP API.

route-tools is created with the database. Set its URL, timeout, and TLS check for the default path. A timeout, a non-object body, or a plan the parser rejects means a ringing inbound call is not answered.

1. Point the route at your server

  1. Open Routes.
  2. Edit the row route-tools.
  3. Set the URL to an endpoint you control. The scheme is http, https, ws, or wss.
  4. Set the timeout in milliseconds. The starting value is 5000.
  5. Turn TLS verification on for a public HTTPS endpoint that has a normal certificate. HTTP and WS always skip certificate checks.
  6. Save.

The first URL in a new database is http://127.0.0.1:9099/route. Replace it before you put the program on a real trunk. If the route row is missing, or the HTTP call fails, DotSIP hangs up. A reply is either one action, or a list of commands. Commands are how you play a recording and collect digits without an agent. That is written out in Recordings and DTMF.

2. What DotSIP sends

It POSTs JSON. Headers are Accept: application/json, Content-Type: application/json, and User-Agent: DotSIP-Route/1. There is no API key and no signature on this request. Put the endpoint on a network you trust, or protect it yourself in front of DotSIP.

{
  "format": "dotsip.route",
  "format_version": 1,
  "direction": "inbound",
  "call_id": "c-7f3a",
  "sip_account": { "id": "trunk", "name": "trunk" },
  "sip_phone": null,
  "dialed": "101",
  "route": "route-tools",
  "caller": {
    "number": "+421901234567",
    "name": null,
    "rights": null,
    "groups": [
      { "id": "recepcia", "label": "Recepcia", "phones": ["101", "102"] }
    ]
  }
}

sip_account is the SIP channel, or null. Both id and name are that channel id. sip_phone is null. dialed is the user part of what was dialed, or null. route is the id being asked. caller.rights is null on this request. The route response assigns the right. caller.groups is empty when the looked-up number is in no group. Each group is id, label, and phones (extension users). caller.name is a directory name, or null. DotSIP does not copy the number into the name.

direction is inbound for a call that arrived, and for an originate with behavior incoming. It is outbound for an originate with behavior outbound. On that outbound POST, caller.number is the extension and dialed is the destination.

3. What you answer

The body must be JSON, at most 65536 bytes. Three actions are handled.

Answer with an agent

{
  "format": "dotsip.route-result",
  "format_version": 1,
  "action": "answer",
  "agent": "agent-main",
  "rights": "right-1",
  "user": "caller-42",
  "prompt": "Greet the caller in Slovak and ask what they need."
}

agent is set only when a voice agent should take the call. answer alone picks up and does not start an agent. rights on this object is the only place a route sets the right for the call. It stays for the whole call. user and prompt are optional. The prompt is appended to that agent’s saved prompt. The voice stays the agent’s voice.

Transfer to an extension

{
  "format": "dotsip.route-result",
  "format_version": 1,
  "action": "transfer",
  "phone": "200"
}

phone is required. DotSIP transfers the call to that extension on this machine.

Hang up

{
  "format": "dotsip.route-result",
  "format_version": 1,
  "action": "hangup"
}

Send hangup with no error field when you want the call rejected. An error field marks the route as failed. Any other action, a missing agent, or a transfer to a phone that does not exist is also a hangup. HTTP status 400 or higher is a failed route and the call is not answered.

4. Minimal endpoint

Each language answers every call with one agent. Switch PHP, Python, or Node.js above the sample. Return HTTP 200. DotSIP reads the body.

<?php
header("Content-Type: application/json");
$raw = file_get_contents("php://input");
$body = json_decode($raw, true);
if (!is_array($body) || (isset($body["format"]) ? $body["format"] : "") !== "dotsip.route") {
    http_response_code(400);
    echo json_encode(array("error" => "bad request"));
    return;
}
echo json_encode(array(
    "format" => "dotsip.route-result",
    "format_version" => 1,
    "action" => "answer",
    "agent" => "agent-main",
    "rights" => "right-1",
));