Send an Outbound Survey with Node.JS

This example will show how you can send an outbound call survey in multiple languages.

Configuring the code

First, we need to create a Javascript object using a JSON object literal. This will contain each of the steps of the survey in English and Spanish. We will open with an introduction to gather input on whether we should continue in English or in Spanish. We'll then ask a question in the chosen language, comment on their response, and define a phrase for if there is no input/unclear input.

const i18n = {
  'intro': {
    'en': "Hello, we have some questions for you. Press 1 for English",
    'es': "Hola, tenemos algunas preguntas para ti. Presione 2 para español"
  'question': {
    'en': "What is your favorite food?",
    'es': "¿Cuál es tu comida favorita?"
  'closing': {
    'en': "That sounds delicious!",
    'es': "¡Eso suena delicioso!"
  'sorry': {
    'en': "Sorry, I did not understand",
    'es': "Perdón no entendí"

Next we will define a function sayWithOptions that will take the verbiage to say, the instantiated response, and the language to use as parameters in order to use text to speech. The .ssmlProsody is a feature that can be used to change the pitch, contour, range, rate, duration, and volume for text to speech. You can read more about that here in order to make the correct changes for your needs, but in our case we will be slowing the speech slightly down.

const sayWithOptions = function(say_what, response, language = 'en') {
    language: language
  }, say_what);

Now that we've defined our survey and our language processing function, we can define the two necessary routes start and question! When a call is sent out, we will always immediately navigate to the start route. The first thing we will do is instantiate VoiceResponse as response. This will allow us to use the SignalWire client for text to speech. /start accepts Digits as a parameter for when the user presses 1 for English or 2 for Spanish. We need to check if the Digits parameter is undefined, which would indicate that this is the first time /start has been called so we need need to play the introductory phrases in both languages. Once we play both phrases, we will wait for the user to press 1 or 2 and then call the /start route with Digits defined this time. When Digits is defined, the code will redirect to the /question route with the correct language included as a query param."/start", (req, res, next) => {
  var response = new RestClient.LaML.VoiceResponse();
  if (req.body.Digits !== undefined){
    if (req.body.Digits == '1'){
    } else if (req.body.Digits == '2') {
    } else {
      sayWithOptions(i18n['intro']['en'], response, 'en')
      sayWithOptions(i18n['intro']['es'], response, 'es')
  } else {
    gather = response.gather({ timeout: 5, numDigits: 1, action: '/start' })
    sayWithOptions(i18n['intro']['en'], gather, 'en')
    sayWithOptions(i18n['intro']['es'], gather, 'es')
  respondAndLog(res, response);

In the /question route, we will ask the user the survey question 'What is your favorite food' in English or Spanish. We start by instantiating VoiceResponse as we did before and defining the lang parameter which as passed through as a query param in the redirect url of the previous route. If the speech result is not undefined, we will repeat it back as a question, call the closing statement in the right language, and hang up. If the speech result is undefined (non existent or unclear), we will repeat the question and try to gather the speech again. Additional failures to gather speech at this point will hang up the call."/question", (req, res, next) => {
  var response = new RestClient.LaML.VoiceResponse();
  var lang = req.query.lang

  if (req.body.SpeechResult !== undefined ) {
    sayWithOptions(req.body.SpeechResult + "?", response, lang)
    sayWithOptions(i18n['closing'][lang], response, lang)
  } else {
    gather = response.gather({ input: 'speech', speechTimeout: 'auto' })
    sayWithOptions(i18n['question'][lang], gather, lang)
  respondAndLog(res, response);

Running the application

To install prerequisites, run npm install.

Configure your application by copying env.example to .env and editing it with your credentials, including the necessary application domain (see below).

To run the application, simply execute node index.js. The application runs on port 3000 by default.

Triggering calls

There are a number of different ways that you could trigger your outbound call, but below are two examples using Node.JS and cURL. You will need your SignalWire Project ID, auth token, and space URL. You can easily find all three values in a copyable format in your SignalWire Dashboard by clicking the API tab on the lefthand sidebar.

Whatever method you choose to use, you will need the Create Call API!

There is already a defined Sinatra route within this script called /trigger in order to initiate the call. This route connects to the SignalWire client in order to create a call and navigate to the /start route once the caller and callee are connected."/trigger", (req, res, next) => {
  const { RestClient } = require('@signalwire/node')
  const client = new RestClient(process.env.SIGNALWIRE_PROJECT_KEY, process.env.SIGNALWIRE_TOKEN, { signalwireSpaceUrl: process.env.SIGNALWIRE_SPACE })

         url: process.env.APP_DOMAIN + '/start',
         to: req.body.toNumber,
         from: process.env.SIGNALWIRE_FROM_NUMBER
      .then(call => console.log(call.sid))

You can also use cURL on your command prompt or with a tool such as postman to send HTTP requests to create the call. You will need to replace the ProjectID and Space URL within the cURL URL, as well as the webhook pointing to this code, the To number, the From number, and the authentication at the bottom.

curl \
  -X POST \
  --data-urlencode "Url=YOUR-WEBHOOK-URL/start" \
  --data-urlencode "To=DESTINATION-NUMBER" \
  --data-urlencode "From=CALLER-ID-FROM-SIGNALWIRE-ACCOUNT" \

Sign Up Here

If you would like to test this example out, you can create a SignalWire account and space here.

Please feel free to reach out to us on our Community Slack or create a Support ticket if you need guidance!

What’s Next

Check out the full code on our SignalWire Github Repo!

Did this page help you?