SWML Overview
SignalWire Markup Language (SWML) allows you to build RELAY applications using a descriptive format which doesn't necessarily require additional infrastructure such as your own server. You can think of SWML as the RELAY equivalent of Compatibility XML. It is, however, expressed in JSON / YAML using the statements and methods defined here. Statements are used for general control flow and state management. Methods are used for call control. SWML Scripts can be written and saved in the RELAY Dashboard of your SignalWire Space.
Here's a basic SWML document:
- YAML
- JSON
version: 1.0.0
sections:
main:
- record
- play: "say:Hello, world!"
- hangup
{
"version": "1.0.0",
"sections": {
"main": [
"record",
{ "play": "say:Hello, world!" },
"hangup"
]
}
}
Document Fetching
A SWML script is fetched as follows:
- SignalWire receives a call (e.g., a PSTN call) to one of the numbers configured to use SWML.
- SignalWire sends a POST request to the configured webhook, with a body containing the set of currently defined script variables (initially empty) and the current call object:
{
"call": {
"project_id": "project uuid",
"space_id": "space_uuid",
"call_id": "uuid",
"state": "created",
"type": "sip",
"from": "sip:+15551231234@example.com",
"to": "sip:+15553214321@example.com",
"headers": [
{ "name": "X-Header-1", "value": "X-Header-1-Value" },
{ "name": "X-Header-2", "value": "X-Header-2-Value" }
]
},
"vars": {
"customer_script_var_1": "customer_script_var_1_value",
"customer_script_var_2": "customer_script_var_2_value"
},
"params": {
"param_1": "param_1_value",
"param_2": "param_2_value"
}
}
- The server replies with a SWML script to execute, using
application/json
orapplication/yaml
ortext/x-yaml
as content-type.
Versioning
At the top level of a script is a version
field, which is a string containing a semantic version of the document schema. Suggested use is for a MAJOR version to reflect breaking changes where a script may not operate correctly without changes (i.e., renaming, refactoring, or new required parameters for existing methods). A MINOR version can be used as new methods or new optional fields for existing methods are introduced. A PATCH version can be used to reflect only the most minor bug fixes which still have some impact on the schema.
Document Execution
Every SWML script contains a sections
field that is a mapping of named sections for
execution. The main
section is always executed first. There must always be a
main
section in the document. The transfer
statement is used to transfer the call execution to a new URL.
Each section is an array of strings or objects. Strings can be used for methods
that do not require parameters, such as answer
,
hangup
, or
denoise
. Use objects to execute methods with
parameters. Each string or object in the section's array is executed in order
until the section is completed. If there are no more methods to execute, the
call ends.
Expression Evaluation with JavaScript
All text wrapped in %{}
is evaluated as JavaScript or as a plain variable substitution.
Variables and Call Parameters
All variables and call parameters are delivered in the POST body to the URL in transfer
and execute
. Use the set
and unset
statements to set or delete variables.
All variables can be referenced inside %{}
JavaScript expressions in the vars.
JavaScript object.
Variables can not be permanently changed inside JavaScript expressions.
All Call object parameters are referenceable inside JavaScript expressions. For example %{call.from}
or %{call.headers.X-Customer-ID}
. Call object parameters are read only.
All variables are global, and their scope corresponds to the scope of the call (not of the section, and not of the script).
Script Example
In this example, the main section is executed to answer the call, play two files, then prompt for DTMF input. If 1 is pressed, the flow loops back to the welcome message. Otherwise the call is hung up.
- YAML
- JSON
version: 1.0.0
sections:
main:
- answer
- label: welcome
- play:
volume: 20
urls:
- https://example.com/welcome.mp3
- https://example.com/music.mp3
- transfer: get-input-1
get-input-1:
- prompt:
- https://example.com/prompt.wav
- goto:
when: prompt_value === '1'
label: welcome
max: 3
- hangup
{
"version": "1.0.0",
"sections": {
"main": [
"answer",
{
"play": {
"volume": 20.0,
"urls": ["https://example.com/welcome.mp3", "https://example.com/music.mp3"]
}
},
{ "prompt": ["https://example.com/prompt.wav"] },
{
"goto": {
"when": "prompt_value === '1'",
"label": "welcome",
"max": 3
}
},
"hangup"
]
}
}
Implementing Subroutines or Integrations
The execute
statement provides a mechanism for calling subroutines. A subroutine is implemented as a section in the document that can optionally accept named parameters and return a result or can be hosted by a server.
The subroutine may optionally be implemented as an object with meta
and code
fields to add user-defined data and the code to execute, respectively.
JSON Subroutine in Script
{
"version": "1.0.0",
"sections": {
"main": [
{
"execute": {
"params": { "file": "https://example.com/foo.wav" },
"meta": { "x": 100, "y": 100 },
"dest": "my_subroutine"
}
},
{
"execute": {
"params": { "file": "https://example.com/bar.wav" },
"dest": "my_subroutine",
"result": {
"case": {
"1": ["hangup"],
"2": [{ "play": "say:You pressed two" }]
},
"default": []
}
}
}
],
"my_subroutine": {
"meta": { "foo": "bar" },
"code": [{ "play": "%{params.file}" }, { "return": "1" }]
}
}
}
JSON Subroutine Hosted by Server
This operates similarly to the previous example, except the script will execute a URL that will reply with the following document:
{
"version": "1.0.0",
"sections": {
"main": [
{ "play": "%{params.file}" },
{ "return": "1" }
]
}
}
Executing SWML from a RELAY application
The following example is a node.js application that listens on a relay application named swml
, and then transfers the call to a relay ML script. The script may be defined in the request or may specify a URL that will return the document in response to a post request to it. You must map a phone number or domain app to the swml
application name in your dashboard.
const { Voice } = require("@signalwire/realtime-api");
var script = `
version: 1.0.0
sections:
main:
- answer
- play:
- "silence:2"
- "say:Please hold while we connect you"
- connect:
to: "+15551231234"
from: "+15553214321"
result:
case:
connected:
- play: "say: Goodbye"
- hangup
default:
- execute: voicemail
voicemail:
- play:
- "say:I'm sorry the person you are trying to reach is not available"
- "say:Please leave a message after the tone"
- record:
beep: true
- play: "say: Thank you. Goodbye."
- hangup
`;
const client = new Voice.Client({
project: "<your project token>",
token: "<your project API key>",
host: "<your space url>/api",
topics: ["swml"],
});
client.on("call.received", async (call) => {
try {
await client.execute({
method: "calling.transfer",
params: {
node_id: call.nodeId,
call_id: call.callId,
dest: script,
},
});
} catch (error) {}
});