Twilio Voice SDK - Implementing a mechanism to make calls to external phone numbers while concealing the number from the frontend

Twilio Voice SDK - Implementing a mechanism to make calls to external phone numbers while concealing the number from the frontend

I've built a mechanism to make calls from a browser to external phone numbers using the Twilio Voice JavaScript SDK and Twilio Functions. Customer information is managed on the server, and by displaying only names and company names on the frontend, calls can be made while keeping phone numbers hidden. I'll organize the key points such as TwiML App settings and Function implementation to create the foundation for a phone number concealment call system.
2025.08.21

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.

Operation image

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

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.

Architecture diagram

  1. Frontend retrieves a list of customer name:phone number from the backend (phone numbers are not displayed in the UI)
  2. Frontend obtains an Access Token from Twilio Function /token
  3. User selects a customer and clicks the call button
  4. Frontend executes a call request using the Twilio Voice SDK with the selected customer's phone number
  5. Twilio retrieves and interprets TwiML from Twilio Function /voice
  6. Twilio transfers the call to the external phone number via PSTN

Implementation

Creating a TwiML App

  • Develop > Phone Numbers > Manage > TwiML Apps
    Creating a TwiML App

  • Friendly name: Any name (e.g., voice-poc-app)

  • For Voice Configuration Request URL, we will set the Twilio Functions URL later
    TwiML App settings

  • Make a note of the TwiML App SID### Obtaining an API Key

  • Account Dashboard > Account Info > API Keys & Tokens
    Obtaining an API Key

  • Friendly name: Any name (example: voice-poc-app-key)

  • Key type: Standard
    API Key settings

  • Make note of the Key SID and Secret

  • Create a service in Develop > Functions and Assets > Services
    create service

  • Service Name: Any name (example: voice-poc-service)

  • Create a Function named /token using Add Function
    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
    visibility settings
  • 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

		

Environment Variables

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

Setting Functions URL in TwiML App### 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.

![Function Deploy Confirmation](https://devio2024-media.developers.io/image/upload/v1755737719/2025/08/21/kvcqplnhafxvpiozqrcw.jpg)

### Start the local server

```bash
node server.js

		

Testing operation

  1. Open http://localhost:3000 in your browser
    Operation 1
  2. Click "Initialize voice function" button and authorize microphone access
    Operation 2
  3. Confirm that the customer list is displayed
    Operation 3
  1. Select a customer and click "Call selected customer" button (phone numbers are not displayed in the frontend UI)
    Operation 4
  2. 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.

Share this article

FacebookHatena blogX

Related articles