Telnyx Conference System Demo

60 minutes build time || Github Repo

Telnyx Conference System demo built on Call Control and node.js.

In this tutorial, you’ll learn how to:

  1. Set up your development environment to use Telnyx Call Control using Node.
  2. Build a simple Telnyx Call Control Conference System using Node.


Prerequisites

Before you get started, you’ll need set up a Mission Control Portal account, buy a number and connect that number to a Connection. You can learn how to do that in the quickstart guide [here] .

You’ll also need to have node installed to continue. You can check this by running the following:

Copy
Copied
$ node -v

If Node isn’t installed, follow the official installation instructions for your operating system to install it.

You’ll need to have the following Node dependencies installed for the Call Control API:

Copy
Copied
require('express');
require('superagent');
require('fs');

Note: After pasting the above content, Kindly check and remove any new line added

Telnyx Call Control Basics

For the Call Control application you’ll need to get a set of basic functions to perform Telnyx Call Control Commands plus Telnyx Call Control Conference specifics.

This tutorial will be using the following subset of basic Telnyx Call Control Commands:

  • [Call Control Answer]
  • [Call Control Hangup]
  • [Call Control Speak]
  • [Call Control Dial]

Plus all the Telnyx Call Control Conference Commands:

  • [Call Control Join Conference]
  • [Call Control Mute Conference Participant]
  • [Call Control Unmute Conference Participant]
  • [Call Control Hold Conference Participant]
  • [Call Control Unhold Conference Participant]

You can get the full set of available Telnyx Call Control Commands [here] .

For each Telnyx Call Control Command we will be creating a function that will execute an HTTP POST Request to back to Telnyx server. To execute this API we are using superagent, so make sure you have it installed. If not you can install it with the following command:

Copy
Copied
$ npm install superagent --save

Note: After pasting the above content, Kindly check and remove any new line added

After that you’ll be able to use superagent as part of your app code as follows:

Copy
Copied
const superagent = require('superagent');

Note: After pasting the above content, Kindly check and remove any new line added

To make use of the Telnyx Call Control Command API you’ll need to set a Telnyx API Key and Secret.

To check that go to Mission Control Portal and under the Auth tab you select Auth V1. Scrolling down you'll find credentials for Call Control.

Once you have them, you can include them in the telnyx-account.json file.

Copy
Copied
"telnyx_api_key_v1": "<your-api-v1-key-here>"
"telnyx_api_secret_v1": "<your-api-v1-secret-here>"

Note: After pasting the above content, Kindly check and remove any new line added

This application will also make use of a hosted audio file for the waiting tone while on [hold] :

Copy
Copied
"telnyx_waiting_url": "<your-path-to-waiting-song-here>"

Note: After pasting the above content, Kindly check and remove any new line added

As well as the Connection ID of the Call Control Connection for the [Dial] command:

Copy
Copied
"telnyx_connection_id": "<your-call-control-connection-id>"

Note: After pasting the above content, Kindly check and remove any new line added

You can find the Call Control Connection ID in the Mission Portal by editing the connection being used:

Connection ID

Once all dependencies are set, we can create a function for each Telnyx Call Control Command. All Commands will follow the same syntax:

Copy
Copied
function call_control_COMMAND_NAME(f_call_control_id, f_INPUT1, ...){
  const cc_action =COMMAND_NAME;

  const request = superagent
    .auth(g_telnyx_key, g_telnyx_secret)
    .post(`https://api.telnyx.com/calls/${f_call_control_id}/actions/${cc_action}`)
    .send({ PARAM1: f_INPUT1 });

  request
    .then((response) => {
      const body = response.body;
    })
    .catch((error) => {
      console.log(error);
    });
}

Note: After pasting the above content, Kindly check and remove any new line added

Understanding the Command Syntax

There are several aspects of this function that deserve some attention:

  • Function Input Parameters : to execute every Telnyx Call Control Command you’ll need to feed your function with the following:
    • the Call Control ID
    • the input parameters, specific to the body of the Command you’re executing.

Having these set as function input parameters will make it generic enough to reuse for different use cases:

Copy
Copied
function call_control_COMMAND_NAME(f_call_control_id, f_INPUT)

Note: After pasting the above content, Kindly check and remove any new line added

All Telnyx Call Control Commands will be expecting the Call Control ID except Dial. There you’ll get a new one for the leg generated as response.

  • Name of the Call Control Command : as detailed [here] , the Command name is part of the API URL. In our code we call that the action name, and will feed the POST Request URL later:
Copy
Copied
const cc_action =COMMAND_NAME;

Note: After pasting the above content, Kindly check and remove any new line added

  • Building the Telnyx Call Control Command : once you have the Command name defined, you should have all the necessary info to build the complete Telnyx Call Control Command:
Copy
Copied
const request = superagent
  .auth(g_telnyx_key, g_telnyx_secret)
  .post(
    `https://api.telnyx.com/calls/${f_call_control_id}/actions/${cc_action}`
  )
  .send({ PARAM: f_INPUT });

Note: After pasting the above content, Kindly check and remove any new line added

Copy
Copied
In this example, you can see that `Call Control ID` and the Action name will feed the URL of the API, both Telnyx Key and Telnyx Secret feed the Authentication headers, and the body will be formed with all of the different input parameters received for that specific Command.
  • Calling the Telnyx Call Control Command : Having the request headers and options / body set, the only thing left is to execute the POST Request to run the command. For that we are making use superagent module:
Copy
Copied
request
  .then((response) => {
    const body = response.body;
  })
  .catch((error) => {
    console.log(error);
  });

Note: After pasting the above content, Kindly check and remove any new line added

Telnyx Call Control Basic Set

This is how every Telnyx Call Control Command used in this application looks:

Call Control Answer

Copy
Copied
function call_control_answer_call(
  f_telnyx_api_key_v1,
  f_telnyx_api_secret_v1,
  f_call_control_id,
  f_client_state_s
) {
  const l_cc_action = 'answer';
  const l_client_state_64 = null;

  if (f_client_state_s) {
    l_client_state_64 = Buffer.from(f_client_state_s).toString('base64');
  }

  const request = superagent
    .auth(f_telnyx_api_key_v1, f_telnyx_api_secret_v1)
    .post(
      `https://api.telnyx.com/calls/${f_call_control_id}/actions/${cc_action}`
    )
    .send({ client_state: l_client_state_64 }); // if inbound call === null

  request
    .then((response) => {
      const body = response.body;
    })
    .catch((error) => {
      console.log(error);
    });
}

Note: After pasting the above content, Kindly check and remove any new line added

Call Control Hangup

Copy
Copied
function call_control_hangup(
  f_telnyx_api_key_v1,
  f_telnyx_api_secret_v1,
  f_call_control_id
) {
  const l_cc_action = 'hangup';

  const request = superagent
    .auth(f_telnyx_api_key_v1, f_telnyx_api_secret_v1)
    .post(
      `https://api.telnyx.com/calls/${f_call_control_id}/actions/${cc_action}`
    )
    .send({});

  request
    .then((response) => {
      const body = response.body;
    })
    .catch((error) => {
      console.log(error);
    });
}

Note: After pasting the above content, Kindly check and remove any new line added

Call Control Dial

Copy
Copied
function call_control_dial(
  f_telnyx_api_key_v1,
  f_telnyx_api_secret_v1,
  f_dest,
  f_from,
  f_connection_id
) {
  const l_cc_action = 'dial';

  const request = superagent
    .auth(f_telnyx_api_key_v1, f_telnyx_api_secret_v1)
    .post(
      `https://api.telnyx.com/calls/${f_call_control_id}/actions/${cc_action}`
    )
    .send({
      to: f_dest,
      from: f_from,
      connection_id: f_connection_id,
    });

  request
    .then((response) => {
      const body = response.body;
    })
    .catch((error) => {
      console.log(error);
    });
}

Note: After pasting the above content, Kindly check and remove any new line added

Call Control Speak

Copy
Copied
function call_control_speak(
  f_telnyx_api_key_v1,
  f_telnyx_api_secret_v1,
  f_call_control_id,
  f_tts_text
) {
  const cc_action = 'speak';

  const request = superagent
    .auth(f_telnyx_api_key_v1, f_telnyx_api_secret_v1)
    .post(
      `https://api.telnyx.com/calls/${f_call_control_id}/actions/${cc_action}`
    )
    .send({
      payload: f_tts_text,
      voice: g_ivr_voice,
      language: g_ivr_language,
    });

  request
    .then((response) => {
      const body = response.body;
    })
    .catch((error) => {
      console.log(error);
    });
}

Note: After pasting the above content, Kindly check and remove any new line added

Telnyx Call Control Conference Commands

This is what every Telnyx Call Control Conference Commands look like:

Conference: Create Conference

Copy
Copied
function call_control_create_conf(
  f_telnyx_api_key_v1,
  f_telnyx_api_secret_v1,
  f_call_control_id,
  f_client_state_s,
  f_name,
  f_callback
) {
  const l_cc_action = 'create_conf';
  const l_client_state_64 = null;

  if (f_client_state_s) {
    l_client_state_64 = Buffer.from(f_client_state_s).toString('base64');
  }

  const request = superagent
    .auth(f_telnyx_api_key_v1, f_telnyx_api_secret_v1)
    .post(`https://api.telnyx.com/conferences`)
    .send({
      call_control_id: f_call_control_id,
      name: f_name,
      client_state: l_client_state_64,
    });

  request
    .then((response) => {
      const body = response.body;
      f_callback(null, body.data.id);
    })
    .catch((error) => {
      console.log(error);
      f_callback(error);
    });
}

Note: After pasting the above content, Kindly check and remove any new line added

Conference: Join Conference

Copy
Copied
function call_control_join_conf(
  f_telnyx_api_key_v1,
  f_telnyx_api_secret_v1,
  f_call_control_id,
  f_conf_id,
  f_client_state_s
) {
  const l_cc_action = 'join';
  const l_client_state_64 = null;

  if (f_client_state_s) {
    l_client_state_64 = Buffer.from(f_client_state_s).toString('base64');
  }

  const request = superagent
    .auth(f_telnyx_api_key_v1, f_telnyx_api_secret_v1)
    .post(
      `https://api.telnyx.com/conferences/${f_conf_id}/actions/${l_cc_action}`
    )
    .send({
      call_control_id: f_call_control_id,
      client_state: l_client_state_64,
    });

  request
    .then((response) => {
      const body = response.body;
      f_callback(null, body.data.id);
    })
    .catch((error) => {
      console.log(error);
    });
}

Note: After pasting the above content, Kindly check and remove any new line added

Conference: Mute Participant

Copy
Copied
function call_control_mute(
  f_telnyx_api_key_v1,
  f_telnyx_api_secret_v1,
  f_conf_id,
  f_call_control_ids
) {
  const l_cc_action = 'mute';

  const request = superagent
    .auth(f_telnyx_api_key_v1, f_telnyx_api_secret_v1)
    .post(
      `https://api.telnyx.com/conferences/${f_conf_id}/actions/${l_cc_action}`
    )
    .send({ call_control_ids: f_call_control_ids });

  request
    .then((response) => {
      const body = response.body;
    })
    .catch((error) => {
      console.log(error);
    });
}

Note: After pasting the above content, Kindly check and remove any new line added

Conference: Umute Participant

Copy
Copied
function call_control_unmute(f_telnyx_api_key_v1, f_telnyx_api_secret_v1, f_conf_id, f_call_control_ids) {
    const l_cc_action = 'unmute';

    const request = superagent
      .auth(f_telnyx_api_key_v1, f_telnyx_api_secret_v1)
      .post(`https://api.telnyx.com/conferences/${f_conf_id}/actions/${l_cc_action}`)
      .send({ call_control_ids: f_call_control_ids });

    request
      .then((response) => {
        const body = response.body;
      })
      .catch((error) => {
        console.log(error);
      });

Note: After pasting the above content, Kindly check and remove any new line added

Conference: Hold Participant

Copy
Copied
function call_control_hold(
  f_telnyx_api_key_v1,
  f_telnyx_api_secret_v1,
  f_conf_id,
  f_call_control_ids,
  f_audio_url
) {
  const l_cc_action = 'hold';

  const request = superagent
    .auth(f_telnyx_api_key_v1, f_telnyx_api_secret_v1)
    .post(
      `https://api.telnyx.com/conferences/${f_conf_id}/actions/${l_cc_action}`
    )
    .send({
      call_control_ids: f_call_control_ids,
      audio_url: f_audio_url,
    });

  request
    .then((response) => {
      const body = response.body;
    })
    .catch((error) => {
      console.log(error);
    });
}

Note: After pasting the above content, Kindly check and remove any new line added

Conference: Unhold Participant

Copy
Copied
function call_control_unhold(
  f_telnyx_api_key_v1,
  f_telnyx_api_secret_v1,
  f_conf_id,
  f_call_control_ids
) {
  const l_cc_action = 'unhold';

  const request = superagent
    .auth(f_telnyx_api_key_v1, f_telnyx_api_secret_v1)
    .post(
      `https://api.telnyx.com/conferences/${f_conf_id}/actions/${l_cc_action}`
    )
    .send({ call_control_ids: f_call_control_ids });

  request
    .then((response) => {
      const body = response.body;
    })
    .catch((error) => {
      console.log(error);
    });
}

Note: After pasting the above content, Kindly check and remove any new line added

Client State: within some of the Telnyx Call Control Commands list we presented, you probably noticed we were including the Client State parameter. Client State is the key to ensure that we can have several levels on our IVR while consuming the same Call Control Events.

Because Call Control is stateless and async, your application will be receiving several events of the same type, e.g. user just included DTMF. With Client State you enforce a unique ID to be sent back to Telnyx which will be used within a particular Command flow, identifying it as being at Level 2 of a certain IVR for example.

Building a Conference System

With all the basic and conference related Telnyx Call Control Commands set, we are ready to put them in the order that will create a simple Conference System. For that all we are going to do is to:

  1. handle incoming calls and place participants in the conference
  2. push for outgoing calls and place participants in the conference
  3. maintain a participant list
  4. greet the new participants before place them on the conference room
  5. put the first participant automatically on hold
  6. put a participant on-hold every-time he's the only participant on the conference room
  7. un-hold the unique participant on the conference room when the second arrives
  8. allow remote commands to list participants, force hold/unhold, force mute/unmute, force participant push

To exemplify this process we created a simple API call that will be exposed as the webhook in Mission Portal. For that we would be using express:

Copy
Copied
$ npm install request --save

Note: After pasting the above content, Kindly check and remove any new line added

With express we can create an API wrapper that uses HTTP POST to call our Request Token method:

Copy
Copied
rest.post('/' + g_appName + '/start', function (req, res) {
  // APP CODE GOES HERE
});

Note: After pasting the above content, Kindly check and remove any new line added

This would expose a webhook like the following:

Copy
Copied
https://<webhook_domain>:8081/telnyx-conf/start

You probably noticed that g_appName in the previous point. That is part of a set of global variables we are defining with a certain set of info we know we are going to use in this app: TTS parameters, like voice and language to be used, etc...

For the purpose of maintaining the Conference list and state of the Conference room we also define a set of global variables.

You can set these at the beginning of your code:

Copy
Copied
// Application:
const g_appName = 'telnyx-conf';

// TTS Options
const g_ivr_voice = 'female';
const g_ivr_language = 'en-US';

// Conf Options
var g_conf_id = 'no-conf';
var g_on_hold = 'false';
var g_participants = new Map();

Note: After pasting the above content, Kindly check and remove any new line added

If you would like to run the application on your local machine you will have to expose the app to the public internet. To do this you can use ngrok. You can follow the setup guide for ngrok [here] .

With that set, we can fill in that space that we named as APP CODE GOES HERE. When your webhook URL is ready you can add the webhook URL to your Mission Control Portal Connection associated with your number. Here's an example of what a Call Control setup looks like:

![Mission Control Portal Call Control setup]

So the first thing to be done is to identify the kind of event you just received and extract the Call Control Id and Client State:

Copy
Copied
if (req && req.body && req.body.event_type) {
  var l_hook_event_type = req.body.event_type;
  var l_call_control_id = req.body.payload.call_control_id;
  var l_client_state_64 = req.body.payload.client_state;
} else {
  res.end('0');
}

Note: After pasting the above content, Kindly check and remove any new line added

Once you identify the Event Type received, it’s just a matter of having your application reacting to that. Is the way you react to that Event that helps you creating the IVR logic. What you would be doing is to execute Telnyx Call Control Command as a reaction to those Events.

For consistency, the Telnyx Call Control engine requires every single Webhook to be replied to by the Webhook end-point, otherwise we will keep trying to send it. For that reason, we have to be ready to consume every Webhook we expect to receive and reply with 200 OK.

Webhook Call Initiated >> Command Answer Call

Copy
Copied
if (req.body.payload.direction == 'incoming')
  call_control_answer_call(
    g_telnyx_api_key_v1,
    g_telnyx_api_secret_v1,
    l_call_control_id,
    null
  );
else
  call_control_answer_call(
    g_telnyx_api_key_v1,
    g_telnyx_api_secret_v1,
    l_call_control_id,
    'outgoing'
  );
res.end();

Note: After pasting the above content, Kindly check and remove any new line added

Webhook Call Answered >> Start Conference

Once your app is notified by Telnyx that the call was established you want to either start the conference room or put the participant in an already existing room.

Copy
Copied
if (g_conf_id == 'no-conf') {
  // First participant message
  call_control_speak(
    g_telnyx_api_key_v1,
    g_telnyx_api_secret_v1,
    l_call_control_id,
    'Welcome to this conference demo. ' +
      'Please wait for other participants to join. '
  );

  // Create Conference
  call_control_create_conf(
    g_telnyx_api_key_v1,
    g_telnyx_api_secret_v1,
    l_call_control_id,
    'conf-created',
    'myconf',
    function (conf_err, conf_res) {
      g_conf_id = conf_res;

      // Add Participant to the Participant List
      if (!l_client_state_64)
        g_participants.set(l_call_control_id, l_hook_from);
      // add inbound participant to the list
      else g_participants.set(l_call_control_id, l_hook_to); // add outbound participant to the list
    }
  );
} else {
  // Consequent participants message
  call_control_speak(
    g_telnyx_api_key_v1,
    g_telnyx_api_secret_v1,
    l_call_control_id,
    'Welcome to this conference demo. ' +
      'We are now putting you on the conference room. '
  );

  call_control_join_conf(
    g_telnyx_api_key_v1,
    g_telnyx_api_secret_v1,
    l_call_control_id,
    g_conf_id,
    'agent-in'
  );

  // Add Participant to the Participant List
  g_participants.set(l_call_control_id, l_hook_from); // add participant to the list
}

res.end();

Note: After pasting the above content, Kindly check and remove any new line added

Conference Created >> Just Log

Your app will be informed that the Conference was created.

Copy
Copied
console.log("[%s] LOG - New Conference Created! - Conference ID [%s]", get_timestamp(), g_conf_id);
res.end();
}

Note: After pasting the above content, Kindly check and remove any new line added

Conference Join >> Hold/Unhold Participant

Your app will be informed that a participant just joined the room.

Copy
Copied
if (g_participants.size < 2) {
  // First Participant
  call_control_hold(
    g_telnyx_api_key_v1,
    g_telnyx_api_secret_v1,
    g_conf_id,
    [l_call_control_id],
    g_telnyx_waiting_url
  );
  g_on_hold = l_call_control_id;
} else if (g_participants.size == 2) {
  // Second Participant
  call_control_unhold(g_telnyx_api_key_v1, g_telnyx_api_secret_v1, g_conf_id, [
    g_on_hold,
  ]);
  g_on_hold = 'false';
}
res.end();

Note: After pasting the above content, Kindly check and remove any new line added

Conference Leave >> Remove Participant / Cleanup Vars

Your app will be informed that a participant just left the room, we need to cleanup some things.

Copy
Copied
// Remove participant from the list
g_participants.delete(l_call_control_id);

// Reset Conf_Id if conference room empty
if (g_participants.size < 1) {
  g_conf_id = 'no-conf';

  // Put participant back on hold if it's the last one
} else if (g_participants.size == 1) {
  for (var key of g_participants.keys()) {
    // First Participant
    call_control_hold(
      g_telnyx_api_key_v1,
      g_telnyx_api_secret_v1,
      g_conf_id,
      [key],
      g_telnyx_waiting_url
    );
    g_on_hold = key;
  }
}

res.end();

Note: After pasting the above content, Kindly check and remove any new line added

Anything Else >> Just Ack/200ok

Copy
Copied
else if (l_hook_event_type == 'speak_ended' ||
        l_hook_event_type == 'speak_started' ||
        l_hook_event_type == 'speak_ended' ||
        l_hook_event_type == 'playback_ended' ||
        l_hook_event_type == 'call_hangup' ||
        l_hook_event_type == 'gather_ended' ||
        l_hook_event_type == 'call_bridged' ||
        l_hook_event_type == 'dtmf' ||
        l_hook_event_type == 'playback_started') {
        res.end();
}

Note: After pasting the above content, Kindly check and remove any new line added

Interacting with the Conference Room

As part of the process of building a Conference Room, there is also the possibility of interacting with the application to list participants and engage with direct participants. We do that by creating a couple of HTTP GET commands that can be then called by a browser, cURL or Postman.

Listing Participants

https://<webhookdomain>:8081/telnyx-conf/list_

Copy
Copied
rest.get('/' + g_appName + '/list', function (req, res) {
  // Return/Display complete participant list

  if (g_participants.size > 0 && g_conf_id != 'no-conf') {
    var l_list = 'Conference ID: ' + g_conf_id + '\n';
    l_list += '\n';
    l_list += 'Participant List: \n';

    for (var key of g_participants.keys()) {
      l_list += key + ' - [' + g_participants.get(key) + '] \n';
    }

    res.end(l_list);
  } else {
    res.end('no participant or no conference exists');
  }
});

Note: After pasting the above content, Kindly check and remove any new line added

Mute Participant

https://<webhookdomain>:8081/telnyx-conf/mute?participant=x_

Copy
Copied
rest.get('/' + g_appName + '/mute', function (req, res) {
  // Mute specific Participant

  if (g_participants.size > 0 && g_conf_id != 'no-conf') {
    call_control_mute(g_telnyx_api_key_v1, g_telnyx_api_secret_v1, g_conf_id, [
      req.query.participant,
    ]);

    res.end('participant muted [' + req.query.participant + ']');
  } else {
    res.end('no participant or no conference exists');
  }
});

Note: After pasting the above content, Kindly check and remove any new line added

Unute Participant

https://<webhookdomain>:8081/telnyx-conf/unmute?participant=x_

Copy
Copied
rest.get('/' + g_appName + '/unmute', function (req, res) {
  // Un-Mute specific Participant

  if (g_participants.size > 0 && g_conf_id != 'no-conf') {
    call_control_unmute(
      g_telnyx_api_key_v1,
      g_telnyx_api_secret_v1,
      g_conf_id,
      [req.query.participant]
    );

    res.end('participant unmuted [' + req.query.participant + ']');
  } else {
    res.end('no participant or no conference exists');
  }
});

Note: After pasting the above content, Kindly check and remove any new line added

Hold Participant

https://<webhookdomain>:8081/telnyx-conf/hold?participant=x_

Copy
Copied
rest.get('/' + g_appName + '/hold', function (req, res) {
  // Put specific participant on-hold

  if (g_participants.size > 0 && g_conf_id != 'no-conf') {
    call_control_hold(
      g_telnyx_api_key_v1,
      g_telnyx_api_secret_v1,
      g_conf_id,
      [req.query.participant],
      g_telnyx_waiting_url
    );

    res.end('participant on hold [' + req.query.participant + ']');
  } else {
    res.end('no participant or no conference exists');
  }
});

Note: After pasting the above content, Kindly check and remove any new line added

Unhold Participant

https://<webhookdomain>:8081/telnyx-conf/unhold?participant=x_

Copy
Copied
rest.get('/' + g_appName + '/unhold', function (req, res) {
  // Un-hold specific participant

  if (g_participants.size > 0 && g_conf_id != 'no-conf') {
    call_control_unhold(
      g_telnyx_api_key_v1,
      g_telnyx_api_secret_v1,
      g_conf_id,
      [req.query.participant]
    );

    res.end('participant resumed [' + req.query.participant + ']');
  } else {
    res.end('no participant or no conference exists');
  }
});

Note: After pasting the above content, Kindly check and remove any new line added

Pull Participant

https://<webhookdomain>:8081/telnyx-conf/pull?number=x_

Copy
Copied
rest.get('/' + g_appName + '/pull', function (req, res) {
  // Dial-out to specific number to pull participant

  call_control_dial(
    g_telnyx_api_key_v1,
    g_telnyx_api_secret_v1,
    req.query.number,
    'conf',
    g_telnyx_connection_id
  );
  res.end('called ' + req.query.number);
});

Note: After pasting the above content, Kindly check and remove any new line added

Lightning-Up the Application

Finally the last piece of the puzzle is having your application listening for Telnyx Webhooks:

Copy
Copied
var server = rest.listen(8081, function () {
  var host = server.address().address;
  var port = server.address().port;
});

Note: After pasting the above content, Kindly check and remove any new line added