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
  • Buy phone numbers - buy phone numbers for the call participants
  • Link the phone numbers to the Nexmo Application - configure the voice enabled phone numbers you use to mask user 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 Nexmo 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.

Buy phone numbers

You use Nexmo numbers to hide user phone numbers from your application users.

To buy a phone 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 buy the numbers you want.

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));
};

Once the phone numbers have been purchased you link them with your application. This allows Nexmo to send any event that occurs related to each phone number to your web application via a webhook. After configuration you store the phone number for later user.

lib/VoiceProxy.js
/**
 * 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 phone numbers you need to mask communication between your users.

Note: in a production application you choose from a pool of phone 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 user numbers

Once you are sure that the phone numbers are valid, map each user number to a Nexmo 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 Nexmo number from their phone.

Send an SMS to notify each conversation participant of the Nexmo 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 Nexmo 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 Nexmo 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 user phone numbers to Nexmo numbers

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

The call direction can be identified as:

  • The from number is UserA user number and the to number is UserB Nexmo number
  • The from number is UserB user number and the to number is UserA Nexmo 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 Nexmo number is associated with. The from number is always the Nexmo number, the to is a user 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 user 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 user numbers to Nexmo numbers to ensure anonymity, handled an inbound call and proxied that call to another user.

Previous   Next