Voice - Private voice communication

For market place scenarios such as food delivery or taxi and passenger communications, if your users see each other's phone number and talk to each other without you knowing your application cannot control or track the communication threads. By implementing private communication with Voice API you ensure that your users cannot bypass the required or preferred communication workflows and audits.

This tutorial is based on the Private Voice Communication use case. You can download the code from https://github.com/Nexmo/node-voice-proxy.

In this tutorial

You see how to build an Voice proxy for private communication system using Nexmo CLI and Nexmo Node.JS:

  • Create a Voice application - create and configure an application using Nexmo CLI, then configure the webhook endpoints to provide NCCOs and handle changes in Call status
  • Provision virtual numbers - rent and configure the voice enabled virtual numbers you use to mask real numbers
  • Create a Call - create a Call between two users, validate their phone numbers and determine the country the phone number is registered in using Number Insight
  • Handle inbound calls - configure your webhook endpoint to handle incoming voice calls, find the phone number it is associated with and return the NCCO to control the Call
  • Proxy the Call - instruct Nexmo to make a private Call to a phone number

Prerequisites

In order to work through this tutorial you need:

Create an application

A Nexmo application contains the security and configuration information you need to connect to Nexmo endpoints and easily use our products. You make requests to Nexmo endpoints using the security information in the application. When you make a Call, Nexmo sends and retrieves call management information with your webhook endpoints.

You first use Nexmo CLI to create an application for Voice API:

› nexmo app:create voice-proxy https://example.com/proxy-call https://example.com/event

This command returns the UUID (Universally Unique Identifier) that identifies your application.

The parameters are:

  • voice-proxy - the name you give to this application
  • https://example.com/proxy-call - when you receive an inbound call to your virtual number, Nexmo makes a GET request and retrieves the NCCO that controls the call flow from this webhook endpoint
  • https://example.com/event - as the call status changes, Nexmo sends status updates to this webhook endpoint

Then start your Web server:

lib/server.js
"use strict";

var express = require('express');
var bodyParser = require('body-parser');

var app = express();
app.set('port', (process.env.PORT || 5000));
app.use(bodyParser.urlencoded({ extended: false }));

var config = require(__dirname + '/../config');

var VoiceProxy = require('./VoiceProxy');
var voiceProxy = new VoiceProxy(config);

app.listen(app.get('port'), function() {
  console.log('Voice Proxy App listening on port', app.get('port'));
});

If you're developing behind a firewall or a NAT, use ngrok to tunnel access to your Web server.

Provision virtual numbers

You use virtual numbers to hide real phone numbers from your application users.

The workflow to provision and configure a virtual number is:

To provision a virtual number you search through the available numbers that meet your criteria. For example, a phone number in a specific country with voice capability:

lib/VoiceProxy.js
"use strict";

var Promise = require('bluebird');
var Nexmo = require('nexmo');

/**
 * Create a new VoiceProxy
 */
var VoiceProxy = function(config) {
  this.config = config;

  this.nexmo = new Nexmo({
      apiKey: this.config.NEXMO_API_KEY, 
      apiSecret: this.config.NEXMO_API_SECRET
    },{
      debug: this.config.NEXMO_DEBUG
    });

  // Virtual Numbers to be assigned to UserA and UserB
  this.provisionedNumbers = [].concat(this.config.PROVISIONED_NUMBERS);

  // In progress conversations
  this.conversations = [];
};

/**
 * Provision two virtual numbers. Would provision more in a real app.
 */
VoiceProxy.prototype.provisionVirtualNumbers = function() {
  // Buy a UK number with VOICE capabilities.
  // For this example we'll also get SMS so we can send them a text notification
  this.nexmo.number.search('GB', {features: 'VOICE,SMS'}, function(err, res) {
    if(err) {
      console.error(err);
    }
    else {
      var numbers = res.numbers;

      // For demo purposes:
      // - Assume that at least two numbers will be available
      // - Rent just two virtual numbers: one for each conversation participant
      this.rentNumber(numbers[0]);
      this.rentNumber(numbers[1]);
    }
  }.bind(this));
};

Then rent the numbers you want and associate them with your application. When any even occurs relating to each number associated with an application, Nexmo sends a request to your webhook endpoint with information about the event. After configuration you store the phone number for later user.

lib/VoiceProxy.js
/**
 * Rent the given numbers
 */
VoiceProxy.prototype.rentNumber = function(number) {
  this.nexmo.number.buy(number.country, number.msisdn, function(err, res) {
    if(err) {
      console.error(err);
    }
    else {
      this.configureNumber(number);
    }
  }.bind(this));
};

/**
 * Configure the number to be associated with the Voice Proxy application.
 */
VoiceProxy.prototype.configureNumber = function(number) {
  var options = {
    voiceCallbackType: 'app',
    voiceCallbackValue: this.config.NEXMO_APP_ID,
  };
  this.nexmo.number.update(number.country, number.msisdn, options, function(err, res) {
    if(err) {
      console.error(err);
    }
    else {
      this.provisionedNumbers.push(number);
    }
  }.bind(this));
};

You now have the virtual numbers you need to mask communication between your users.

Note: in a production application you choose from a pool of virtual numbers. However, you should keep this functionality in place to rent additional numbers on the fly.

Create a Call

The workflow to create a Call is:

The following call:

lib/VoiceProxy.js
/**
 * Create a new tracked conversation so there is a real/virtual mapping of numbers.
 */
VoiceProxy.prototype.createConversation = function(userANumber, userBNumber, cb) {
  this.checkNumbers(userANumber, userBNumber)
    .then(this.saveConversation.bind(this))
    .then(this.sendSMS.bind(this))
    .then(function(conversation) {
      cb(null, conversation);
    })
    .catch(function(err) {
      cb(err);
    });
};

Validate the phone numbers

When your application users supply their phone numbers use Number Insight to ensure that they are valid. You can also see which country the phone numbers are registered in:

lib/VoiceProxy.js
/**
 * Ensure the given numbers are valid and which country they are associated with.
 */
VoiceProxy.prototype.checkNumbers = function(userANumber, userBNumber) {
  var niGetPromise = Promise.promisify(this.nexmo.numberInsight.get, {context: this.nexmo.numberInsight});
  var userAGet = niGetPromise({level: 'basic', number: userANumber});
  var userBGet = niGetPromise({level: 'basic', number: userBNumber});

  return Promise.all([userAGet, userBGet]);
};

Map phone numbers to real numbers

Once you are sure that the phone numbers are valid, map each real number to a virtual number and save the call:

lib/VoiceProxy.js
/**
 * Store the conversation information.
 */
VoiceProxy.prototype.saveConversation = function(results) {
  var userAResult = results[0];
  var userANumber = {
    msisdn: userAResult.international_format_number,
    country: userAResult.country_code
  };

  var userBResult = results[1];
  var userBNumber = {
    msisdn: userBResult.international_format_number,
    country: userBResult.country_code
  };

  // Create conversation object - for demo purposes:
  // - Use first indexed LVN for user A
  // - Use second indexed LVN for user B
  var conversation = {
    userA: {
      realNumber: userANumber,
      virtualNumber: this.provisionedNumbers[0]
    },
    userB: {
      realNumber: userBNumber,
      virtualNumber: this.provisionedNumbers[1]
    }
  };

  this.conversations.push(conversation);

  return conversation;
};

Send a confirmation SMS

In a private communication system, when one user contacts another, he or she calls a virtual number from their phone.

Send an SMS to notify each conversation participant of the virtual number they need to call:

lib/VoiceProxy.js
/**
 * Send an SMS to each conversation participant so they know each other's
 * virtual number and can call either other via the proxy.
 */
VoiceProxy.prototype.sendSMS = function(conversation) {
  // Send UserA conversation information
  // From the UserB virtual number
  // To the UserA real number
  this.nexmo.message.sendSms(conversation.userB.virtualNumber.msisdn,
                             conversation.userA.realNumber.msisdn,
                             'Call this number to talk to UserB');

  // Send UserB conversation information
  // From the UserA virtual number
  // To the UserB real number
  this.nexmo.message.sendSms(conversation.userA.virtualNumber.msisdn,
                             conversation.userB.realNumber.msisdn,
                             'Call this number to talk to UserB');

  return conversation;
};

The users cannot SMS each other. To enable this functionality you need to setup Private SMS communication.

In this tutorial each user has received the virtual number in an SMS. In other systems this could be supplied using email, in-app notifications or a predefined number.

Handle inbound calls

When Nexmo receives an inbound call to your virtual number it makes a request to the webhook endpoint you set when you created a Voice application.

Extract to and from from the inbound webhook and pass them on to the voice proxy business logic.

lib/server.js
app.get('/proxy-call', function(req, res) {
  var from = req.query.from;
  var to = req.query.to;

  var ncco = voiceProxy.getProxyNCCO(from, to);
  res.json(ncco);
});

Reverse map real phone numbers to virtual numbers

Now you know the phone number making the call and the virtual number of the recipient, reverse map the inbound virtual number to the outbound real phone number.

The call direction can be identified as:

  • The from number is UserA real number and the to number is UserB virtual number
  • The from number is UserB real number and the to number is UserA virtual number
lib/VoiceProxy.js
var fromUserAToUserB = function(from, to, conversation) {
  return (from === conversation.userA.realNumber.msisdn &&
          to === conversation.userB.virtualNumber.msisdn);
};
var fromUserBToUserA = function(from, to, conversation) {
  return (from === conversation.userB.realNumber.msisdn &&
          to === conversation.userA.virtualNumber.msisdn);
};

/**
 * Work out real number to virual number mapping between users.
 */
VoiceProxy.prototype.getProxyRoute = function(from, to) { 
  var proxyRoute = null;
  var conversation;
  for(var i = 0, l = this.conversations.length; i < l; ++i) {
    conversation = this.conversations[i];

    // Use to and from to determine the conversation
    var fromUserA = fromUserAToUserB(from, to, conversation);
    var fromUserB = fromUserBToUserA(from, to, conversation);

    if(fromUserA || fromUserB) {
      proxyRoute = {
        conversation: conversation,
        to: fromUserA? conversation.userB : conversation.userA,
        from: fromUserA? conversation.userA : conversation.userB
      };
      break;
    }
  }

  return proxyRoute;
};

With the number looking performed all that's left to do is proxy the call.

Proxy the Call

Proxy the call to the phone number the virtual number is associated with. The from number is always the virtual number, the to is a real phone number.

In order to do this, build up an NCCO (Nexmo Call Control Object). This NCCO uses a talk action to read out some text. When the talk has completed, a connect action forwards the Call to a real number.

lib/VoiceProxy.js
/**
 * Build the NCCO response to intruct Nexmo how to handle the inbound call.
 */
VoiceProxy.prototype.getProxyNCCO = function(from, to) {  
  // Determine how the call should be routed
  var proxyRoute = this.getProxyRoute(from, to);

  if(proxyRoute === null) {
    var errorText = 'No conversation found' +
                    ' from: ' + from +
                    ' to: ' + to;
    throw new Error(errorText);
  }

  // Build the NCCO
  var ncco = [];

  var textAction = {
    action: 'talk',
    text: 'Please wait whilst we connect your call'
  };
  ncco.push(textAction);

  var connectAction = {
    action: 'connect',
    from: proxyRoute.from.virtualNumber.msisdn,
    endpoint: [{
      type: 'phone',
      number: proxyRoute.to.realNumber.msisdn
    }]
  };
  ncco.push(connectAction);

  return ncco;
};

Note: take a look at the NCCO reference for more information.

The NCCO is returned to Nexmo by the web server.

lib/server.js
app.get('/proxy-call', function(req, res) {
  var from = req.query.from;
  var to = req.query.to;

  var ncco = voiceProxy.getProxyNCCO(from, to);
  res.json(ncco);
});

Conclusion

And that's it. You have built a voice proxy for private communication. You provisioned and configured phone numbers, performed number insight, mapped real numbers to virtual numbers to ensure anonymity, handled an inbound call and proxied that call to another user.

Previous   Next