Masked Calling
This guide shows you how to implement the idea described in the Private Voice Communication Use Case. It teaches you how to build a voice proxy with Vonage's Node Server SDK, using virtual numbers to hide participants' real phone numbers. Full source code is also available in our Voice Proxy using the Voice API GitHub repo. Alternatively, you can head to Code Hub to try out the Masked Calling API.
Overview
In some cases, you may need two users to communicate without revealing their personal phone numbers. For example, in a ride-sharing service, users need to coordinate pick-up times without disclosing their contact details, which also helps prevent direct arrangements that could cut into your revenue.
With Vonage's APIs, you can provide participants with temporary numbers that mask their real phone numbers during the call. Once the call ends, the temporary numbers are revoked, ensuring privacy.
Steps
This guide provides instructions for building the application, including:
- Prerequisites
- Code Repository
- Configuration
- Create a Voice API Application
- Create the Web Application
- Provision Virtual Numbers
- Create a Call
- Handle Inbound Calls
- Reverse Map Real Phone Numbers to Virtual Numbers
- Proxy the Call
- Conclusion
- Further Information
Prerequisites
In order to work through this use case you need:
- A Vonage account
- The Vonage CLI installed and configured
Code Repository
There is a GitHub repository containing the code.
Configuration
You need to create a .env file containing the configuration. Instructions for doing that are explained in the GitHub README. As you work through this guide, you can populate your configuration file with the required values for variables such as API key, API secret, Application ID, debug mode, and provisioned numbers.
Create a Voice API Application
A Voice API Application is a Vonage construct. It should not be confused with the application you will write. Instead, it's a "container" for the authentication and configuration settings you need to work with the API.
You can create a Voice API Application with the Vonage CLI. You must provide a name for the application and the URLs of two webhook endpoints: the first is the endpoint that Vonage's APIs will make a request to when you receive an inbound call on your virtual number, and the second is the endpoint where the API can post event data.
Replace the domain name in the following Vonage CLI command with your ngrok domain name (To learn more about how to do this, refer to the How to run ngrok guide) and run it in your project's root directory:
This command creates a file called voice_proxy.key that contains authentication information and returns a unique application ID. Make a note of this ID because you'll need it in subsequent steps.
Create the Web Application
This application uses the Express framework for routing and the Vonage Node Server SDK for working with the Voice API. dotenv is used so the application can be configured using a .env text file.
In server.js, the code initializes the application's dependencies and starts the web server. A route handler is implemented for the application's home page (/) so that you can test that the server is running by running node server.js and visiting http://localhost:3000 in your browser:
"use strict";
const express = require('express');
const bodyParser = require('body-parser');
const app = express();
app.set('port', (process.env.PORT || 3000));
app.use(bodyParser.urlencoded({ extended: false }));
const config = require(__dirname + '/../config');
const VoiceProxy = require('./VoiceProxy');
const voiceProxy = new VoiceProxy(config);
app.listen(app.get('port'), function() {
console.log('Voice Proxy App listening on port', app.get('port'));
});
Note that the code instantiates an object of the VoiceProxy class to handle the routing of messages sent to your virtual number to the intended recipient's real number. The proxying process is described in the Proxy the Call chapter of this guide. For now, be aware that this class initializes the Vonage Server SDK using the API key and secret that you will configure in the next step. This setup enables your application to make and receive voice calls:
const VoiceProxy = function(config) {
this.config = config;
this.nexmo = new Nexmo({
apiKey: this.config.VONAGE_API_KEY,
apiSecret: this.config.VONAGE_API_SECRET
},{
debug: this.config.VONAGE_DEBUG
});
// Virtual Numbers to be assigned to UserA and UserB
this.provisionedNumbers = [].concat(this.config.PROVISIONED_NUMBERS);
// In progress conversations
this.conversations = [];
};
Provision Virtual Numbers
Virtual numbers are used to hide real phone numbers from your application users.
The following workflow diagram shows the process for provisioning and configuring a virtual number:
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:
const Nexmo = require('nexmo');
/**
* Create a new VoiceProxy
*/
const 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 {
const 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.
NOTE: Some types of numbers may require you to provide more information, such as a postal address. If you are not able to obtain a number programmatically, you can visit the Developer Dashboard to make a request for such numbers. Keep in mind that some numbers may require information to be asked and the process will not be fully automated.
When an event occurs related to any number associated with an application, Vonage will send a request to your webhook endpoint with information about the event. After the configuration, be sure to store the phone number for later use:
/**
* 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) {
const 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));
};
To provision virtual numbers, visit http://localhost:3000/numbers/provision in your browser.
You now have the virtual numbers necessary to mask communication between your users.
NOTE: In a production application, you will choose from a pool of virtual numbers. However, it's important to keep this functionality in place to rent additional numbers on the fly.
Create a Call
The workflow to create a call is as presented in the diagram:
The following call:
/**
* 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:
/**
* Ensure the given numbers are valid and which country they are associated with.
*/
VoiceProxy.prototype.checkNumbers = function(userANumber, userBNumber) {
const niGetPromise = (number) => new Promise ((resolve) => {
this.nexmo.numberInsight.get(number, (error, result) => {
if(error) {
console.error('error',error);
}
else {
return resolve(result);
}
})
});
const userAGet = niGetPromise({level: 'basic', number: userANumber});
const 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:
/**
* Store the conversation information.
*/
VoiceProxy.prototype.saveConversation = function(results) {
let userAResult = results[0];
let userANumber = {
msisdn: userAResult.international_format_number,
country: userAResult.country_code
};
let userBResult = results[1];
let 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
let 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, the caller calls a virtual number from their phone.
Send an SMS to notify each conversation participant of the virtual number they need to call:
/**
* 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 set up Private SMS communication.
In this guide, 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 Vonage receives an inbound call to your virtual number, it makes a request to the webhook endpoint you set when you created a Voice API Application:
Extract to and from from the inbound webhook and pass them on to the voice proxy business logic:
app.get('/proxy-call', function(req, res) {
const from = req.query.from;
const to = req.query.to;
const 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
fromnumber is UserA real number and thetonumber is UserB Vonage number - The
fromnumber is UserB real number and thetonumber is UserA Vonage number
const fromUserAToUserB = function(from, to, conversation) {
return (from === conversation.userA.realNumber.msisdn &&
to === conversation.userB.virtualNumber.msisdn);
};
const fromUserBToUserA = function(from, to, conversation) {
return (from === conversation.userB.realNumber.msisdn &&
to === conversation.userA.virtualNumber.msisdn);
};
/**
* Work out real number to virtual number mapping between users.
*/
VoiceProxy.prototype.getProxyRoute = function(from, to) {
let proxyRoute = null;
let conversation;
for(let i = 0, l = this.conversations.length; i < l; ++i) {
conversation = this.conversations[i];
// Use to and from to determine the conversation
const fromUserA = fromUserAToUserB(from, to, conversation);
const 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 lookup 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, and the to is a real phone number.
To do this, create 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.
/**
* Build the NCCO response to instruct Nexmo how to handle the inbound call.
*/
VoiceProxy.prototype.getProxyNCCO = function(from, to) {
// Determine how the call should be routed
const proxyRoute = this.getProxyRoute(from, to);
if(proxyRoute === null) {
const errorText = 'No conversation found' +
' from: ' + from +
' to: ' + to;
throw new Error(errorText);
}
// Build the NCCO
let ncco = [];
const textAction = {
action: 'talk',
text: 'Please wait whilst we connect your call'
};
ncco.push(textAction);
const connectAction = {
action: 'connect',
from: proxyRoute.from.virtualNumber.msisdn,
endpoint: [{
type: 'phone',
number: proxyRoute.to.realNumber.msisdn
}]
};
ncco.push(connectAction);
return ncco;
};
The NCCO is returned to Vonage by the web server.
app.get('/proxy-call', function(req, res) {
const from = req.query.from;
const to = req.query.to;
const ncco = voiceProxy.getProxyNCCO(from, to);
res.json(ncco);
});
Conclusion
You have learned how to build 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 the call to another user.