
Twilio Voice SDK - Implementing a mechanism to make calls to external phone numbers while concealing the number from the frontend
Introduction
This article explains how to implement voice call functionality using the Twilio Voice service. As an example, we will implement a feature to make calls to external phone numbers from a frontend application implemented in JavaScript. The following image shows the UI of the application we will implement.
You can select a customer name and make a call while keeping the number hidden. Twilio Voice is a strong option for building systems where direct communication between customers and service providers is necessary while keeping each other's phone numbers confidential, such as ride-sharing apps or delivery services.
What is Twilio?
Twilio is a cloud service that provides communication features such as voice calls, SMS, and video calls as APIs. Developers can integrate communication functions into applications through REST APIs or SDKs without building complex communication infrastructure.
Target Audience
- Application developers interested in implementing voice call functionality
- Those using Twilio services for the first time
- Those with basic knowledge of JavaScript and Node.js
- Those who want to know about practical use cases for WebRTC
References
- Twilio Voice JavaScript SDK Documentation
- Twilio Voice Quickstart for JavaScript
- TwiML Voice Reference
Overview and Architecture Diagram
For the voice calling feature, we use the Twilio Voice JavaScript SDK from the frontend to make calls to regular phone numbers. The phone numbers are masked in the frontend implementation, and only customer names are displayed in the UI.
- Frontend retrieves a list of customer name:phone number from the backend (phone numbers are not displayed in the UI)
- Frontend obtains an Access Token from Twilio Function /token
- User selects a customer and clicks the call button
- Frontend executes a call request using the Twilio Voice SDK with the selected customer's phone number
- Twilio retrieves and interprets TwiML from Twilio Function /voice
- Twilio transfers the call to the external phone number via PSTN
Implementation
Creating a TwiML App
-
Develop > Phone Numbers > Manage > TwiML Apps
-
Friendly name: Any name (e.g.,
voice-poc-app
) -
For Voice Configuration Request URL, we will set the Twilio Functions URL later
-
Make a note of the TwiML App SID### Obtaining an API Key
-
Account Dashboard > Account Info > API Keys & Tokens
-
Friendly name: Any name (example:
voice-poc-app-key
) -
Key type: Standard
-
Make note of the Key SID and Secret
-
Create a service in Develop > Functions and Assets > Services
-
Service Name: Any name (example:
voice-poc-service
) -
Create a Function named
/token
using Add Function
Implementation of /token
exports.handler = function(context, event, callback) {
const res = new Twilio.Response();
res.appendHeader('Content-Type', 'application/json');
res.appendHeader('Access-Control-Allow-Origin', '*');
res.appendHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS');
res.appendHeader('Access-Control-Allow-Headers', 'Content-Type');
if (event.httpMethod === 'OPTIONS') {
res.setStatusCode(200); res.setBody(''); return callback(null, res);
}
const AccessToken = Twilio.jwt.AccessToken;
const VoiceGrant = AccessToken.VoiceGrant;
const identity = 'user001';
const token = new AccessToken(context.ACCOUNT_SID, context.API_KEY_SID, context.API_KEY_SECRET, { identity });
token.addGrant(new VoiceGrant({ outgoingApplicationSid: context.TWIML_APP_SID, incomingAllow: true }));
res.setBody({ identity, token: token.toJwt() });
callback(null, res);
};
- Change the Visibility to Public
- After making changes, don't forget to click Save and Deploy All
Setting Environment Variables
- Set the following values in Environment Variables. You can obtain the ACCOUNT_SID from the Twilio console account dashboard. For CALLER_ID, specify a number purchased from Twilio.
ACCOUNT_SID=ACxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
API_KEY_SID=SKxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
API_KEY_SECRET=your_api_key_secret
TWIML_APP_SID=APxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
CALLER_ID=+81xxxxxxxxxx
Setting the Functions URL in TwiML App
After deploying the Function, set the /voice Function URL as the Voice Request URL in the TwiML App. Actual PSTN calls will be made through this TwiML App when making calls.
- Console > TwiML Apps > Select the created app
- Voice Request URL:
https://voice-poc-service-xxxx-dev.twil.io/voice
### Backend Implementation
We will create a temporary part responsible for customer information management (including phone numbers). In the actual implementation, this is expected to include DB connections and frontend session management.
mkdir twilio-voice-poc
cd twilio-voice-poc
npm init
npm install express
server.js implementation
const express = require('express');
const app = express();
app.use(express.static('public'));
app.get('/customers', (_req, res) => {
const customers = [
{ id: 'c1', name: 'Takumi Koshii', company: 'Classmethod', phoneNumber: '+819012345678' },
{ id: 'c2', name: 'Hanako Sato', company: 'XYZ Corporation', phoneNumber: '+819087654321' },
{ id: 'c3', name: 'Jiro Yamada', company: 'Sample Store', phoneNumber: '+819011111111' },
];
res.json({ customers, count: customers.length });
});
app.listen(3000, () => {
console.log('http://localhost:3000');
});
:::### Frontend Implementation
This provides the functionality needed for end-users to make calls to customers and the number masking UI. This is the core part of this article.
Download twilio.min.js
from Twilio Voice JavaScript SDK Releases and place it in the public
folder.
twilio-voice-poc/
├── server.js
├── public/
│ ├── index.html
│ ├── app.js
│ └── twilio.min.js ← File downloaded from GitHub
public/index.html implementation
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<title>Twilio Voice PoC</title>
<style>
.customer-item{border:1px solid #ccc;margin:5px 0;padding:10px;border-radius:5px;cursor:pointer}
.customer-item:hover{background:#f0f0f0}
.customer-item.selected{background:#e3f2fd;border-color:#2196f3}
</style>
</head>
<body>
<h1>Voice Call Test (Number Masking)</h1>
<div id="initSection">
<p>Microphone permission is required to use voice calls.</p>
<button id="initButton">Initialize Voice Functionality</button>
<div id="initStatus">Waiting for initialization</div>
</div>
<div id="callSection" style="display:none">
<h3>Please select a customer</h3>
<div id="customerList">Loading...</div>
<div style="margin:20px 0">
<button id="callButton" disabled>Call Selected Customer</button>
<button id="hangupButton" disabled>Hang Up</button>
</div>
<div id="callStatus">Ready</div>
<div id="selectedCustomer"></div>
</div>
<script src="twilio.min.js"></script>
<script src="app.js"></script>
</body>
</html>
public/app.js implementation
const Device = Twilio.Device;
const FUNCTIONS_BASE_URL = 'https://voice-poc-service-xxxx.twil.io'; // Specify Twilio Function URL
let device;
let activeCall = null;
let selectedCustomer = null;
async function initializeTwilio() {
// Microphone permission
await navigator.mediaDevices.getUserMedia({ audio: true, video: false });
// Get token
const { token } = await (await fetch(`${FUNCTIONS_BASE_URL}/token`)).json();
// Create Device → register
device = new Device(token);
await device.register();
// Switch UI
document.getElementById('initSection').style.display = 'none';
document.getElementById('callSection').style.display = 'block';
// Manage all subsequent call events through device
device.on('connect', (call) => {
activeCall = call;
document.getElementById('callStatus').textContent = 'In call...';
document.getElementById('callButton').disabled = true;
document.getElementById('hangupButton').disabled = false;
call.on('disconnect', () => {
activeCall = null;
document.getElementById('callStatus').textContent = 'Call ended';
document.getElementById('callButton').disabled = false;
document.getElementById('hangupButton').disabled = true;
});
});
device.on('tokenWillExpire', async () => {
const { token } = await (await fetch(`${FUNCTIONS_BASE_URL}/token`)).json();
await device.updateToken(token);
});
}
// Load & display customer list (phone number not displayed in UI)
async function loadCustomers() {
const { customers } = await (await fetch('/customers')).json();
const list = document.getElementById('customerList');
list.innerHTML = customers.map(c => `
<div class="customer-item" data-customer='${JSON.stringify(c)}'>
<strong>${c.name}</strong><br><small>${c.company}</small>
</div>`).join('');
[...document.querySelectorAll('.customer-item')].forEach(el => {
el.addEventListener('click', () => {
[...document.querySelectorAll('.customer-item')].forEach(i => i.classList.remove('selected'));
el.classList.add('selected');
selectedCustomer = JSON.parse(el.dataset.customer);
document.getElementById('callButton').disabled = false;
document.getElementById('selectedCustomer').textContent =
`Selected: ${selectedCustomer.name} (${selectedCustomer.company})`;
});
});
}
// Initialize button
document.getElementById('initButton').addEventListener('click', async () => {
await initializeTwilio();
await loadCustomers();
});
// Make call
document.getElementById('callButton').addEventListener('click', () => {
if (!selectedCustomer) return;
document.getElementById('callStatus').textContent = 'Calling...';
device.connect({ params: { To: selectedCustomer.phoneNumber } }); // ✅
});
// Hang up
document.getElementById('hangupButton').addEventListener('click', () => {
activeCall?.disconnect();
});
```## Operation verification
### Confirm Twilio Functions Deployment
Confirm that both Functions have been deployed successfully in the Functions Console.

### Start the local server
```bash
node server.js
Testing operation
- Open
http://localhost:3000
in your browser
- Click "Initialize voice function" button and authorize microphone access
- Confirm that the customer list is displayed
- Select a customer and click "Call selected customer" button (phone numbers are not displayed in the frontend UI)
- Confirm that calls can be made successfully between the app and external phone numbers
Summary
In this article, we built a system that combines Twilio Voice JavaScript SDK and Twilio Functions to make calls from a browser to external phone numbers. By managing customer information on the server side while only displaying names and company names on the frontend, we can establish calls while keeping phone numbers hidden from users. This approach can be used not only for ride-hailing apps and delivery services but also in any scenario where you want to enable calls between users without sharing phone numbers.
In the next article, we will introduce the implementation of receiving calls as the next step after the outbound call process. By enabling bidirectional communication, we can build a number-masking call system that is closer to actual operational requirements.