Skip to content

Getting Started

Terminal window
npm install @capgo/capacitor-twilio-voice
npx cap sync

You’ll need a Twilio account and access tokens for authentication. Sign up at Twilio if you don’t have an account.

Add required permissions to your Info.plist:

<key>NSMicrophoneUsageDescription</key>
<string>This app needs microphone access for voice calls</string>

For incoming calls with PushKit:

  1. Enable Push Notifications in Xcode capabilities
  2. Add VoIP background mode
  3. Configure VoIP certificate in Twilio console

Add required permissions to your AndroidManifest.xml:

<uses-permission android:name="android.permission.RECORD_AUDIO" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />
<uses-permission android:name="android.permission.USE_FULL_SCREEN_INTENT" />

Configure Firebase for push notifications:

  1. Add google-services.json to your Android project
  2. Configure FCM in Twilio console
import { TwilioVoice } from '@capgo/capacitor-twilio-voice';
// Authenticate with Twilio
await TwilioVoice.login({
accessToken: 'your-twilio-access-token'
});
// Make a call
await TwilioVoice.makeCall({
to: '+1234567890'
});
// Listen for call events
TwilioVoice.addListener('callConnected', (data) => {
console.log('Call connected:', data.callSid);
});
TwilioVoice.addListener('callDisconnected', (data) => {
console.log('Call disconnected:', data.callSid);
});
TwilioVoice.addListener('incomingCall', (data) => {
console.log('Incoming call from:', data.from);
// Accept the call
TwilioVoice.acceptCall({ callSid: data.callSid });
// Or reject it
// TwilioVoice.rejectCall({ callSid: data.callSid });
});
// Mute/unmute call
await TwilioVoice.muteCall({
muted: true,
callSid: 'current-call-sid'
});
// Toggle speaker
await TwilioVoice.setSpeaker({
enabled: true
});
// End call
await TwilioVoice.endCall({
callSid: 'current-call-sid'
});
// Logout
await TwilioVoice.logout();
login(options: { accessToken: string }) => Promise<void>

Authenticate with Twilio using an access token.

ParamType
options{ accessToken: string }
logout() => Promise<void>

End user session and clear call state.

isLoggedIn() => Promise<{ loggedIn: boolean }>

Check current authentication status.

Returns: Promise<{ loggedIn: boolean }>

makeCall(options: { to: string; params?: Record<string, string> }) => Promise<{ callSid: string }>

Initiate an outgoing call to a specified number.

ParamType
options{ to: string; params?: Record<string, string> }

Returns: Promise<{ callSid: string }>

acceptCall(options: { callSid: string }) => Promise<void>

Accept an incoming call.

ParamType
options{ callSid: string }
rejectCall(options: { callSid: string }) => Promise<void>

Reject an incoming call.

ParamType
options{ callSid: string }
endCall(options?: { callSid?: string }) => Promise<void>

Terminate an active call.

ParamType
options{ callSid?: string } (optional)
muteCall(options: { muted: boolean; callSid?: string }) => Promise<void>

Mute or unmute call audio.

ParamType
options{ muted: boolean; callSid?: string }
setSpeaker(options: { enabled: boolean }) => Promise<void>

Toggle speaker output.

ParamType
options{ enabled: boolean }
sendDigits(options: { digits: string; callSid?: string }) => Promise<void>

Send DTMF tones during a call.

ParamType
options{ digits: string; callSid?: string }
  • registered - Successfully registered with Twilio
  • unregistered - Unregistered from Twilio
  • registrationFailed - Registration failed
  • incomingCall - Incoming call received
  • callConnected - Call successfully connected
  • callDisconnected - Call disconnected
  • callRinging - Outgoing call is ringing
  • callReconnecting - Call is reconnecting
  • callReconnected - Call reconnected after interruption
  • qualityWarning - Call quality warning
  • error - An error occurred
// Handle incoming calls
TwilioVoice.addListener('incomingCall', (data) => {
console.log('Incoming call from:', data.from);
console.log('Call SID:', data.callSid);
// Show incoming call UI
showIncomingCallScreen({
from: data.from,
callSid: data.callSid
});
});
// Handle call state changes
TwilioVoice.addListener('callConnected', (data) => {
console.log('Call connected:', data.callSid);
startCallTimer();
});
TwilioVoice.addListener('callDisconnected', (data) => {
console.log('Call ended:', data.callSid);
stopCallTimer();
hideCallScreen();
});
// Handle quality warnings
TwilioVoice.addListener('qualityWarning', (data) => {
console.warn('Call quality warning:', data.warning);
showQualityWarning(data.warning);
});
// Handle errors
TwilioVoice.addListener('error', (error) => {
console.error('Twilio error:', error.message);
handleError(error);
});
// Remove listeners when done
const listener = await TwilioVoice.addListener('callConnected', (data) => {
console.log('Connected');
});
// Later...
listener.remove();
import { TwilioVoice } from '@capgo/capacitor-twilio-voice';
class VoiceCallService {
private currentCallSid: string | null = null;
private isMuted = false;
private isSpeakerOn = false;
async initialize(accessToken: string) {
try {
// Login to Twilio
await TwilioVoice.login({ accessToken });
// Check login status
const { loggedIn } = await TwilioVoice.isLoggedIn();
console.log('Login status:', loggedIn);
// Setup event listeners
this.setupEventListeners();
} catch (error) {
console.error('Failed to initialize:', error);
}
}
setupEventListeners() {
// Registration events
TwilioVoice.addListener('registered', () => {
console.log('Successfully registered with Twilio');
});
TwilioVoice.addListener('registrationFailed', (error) => {
console.error('Registration failed:', error);
});
// Incoming call
TwilioVoice.addListener('incomingCall', async (data) => {
console.log('Incoming call from:', data.from);
const accepted = await this.showIncomingCallDialog(data.from);
if (accepted) {
await TwilioVoice.acceptCall({ callSid: data.callSid });
this.currentCallSid = data.callSid;
} else {
await TwilioVoice.rejectCall({ callSid: data.callSid });
}
});
// Call events
TwilioVoice.addListener('callConnected', (data) => {
console.log('Call connected');
this.currentCallSid = data.callSid;
this.showCallScreen();
});
TwilioVoice.addListener('callDisconnected', () => {
console.log('Call disconnected');
this.currentCallSid = null;
this.hideCallScreen();
});
TwilioVoice.addListener('callRinging', () => {
console.log('Call ringing...');
});
// Quality warnings
TwilioVoice.addListener('qualityWarning', (data) => {
console.warn('Call quality warning:', data.warning);
this.showQualityIndicator(data.warning);
});
}
async makeCall(phoneNumber: string) {
try {
const result = await TwilioVoice.makeCall({
to: phoneNumber,
params: {
// Optional custom parameters
customerId: 'customer-123'
}
});
this.currentCallSid = result.callSid;
console.log('Call initiated:', result.callSid);
} catch (error) {
console.error('Failed to make call:', error);
}
}
async endCall() {
if (this.currentCallSid) {
await TwilioVoice.endCall({ callSid: this.currentCallSid });
this.currentCallSid = null;
}
}
async toggleMute() {
this.isMuted = !this.isMuted;
await TwilioVoice.muteCall({
muted: this.isMuted,
callSid: this.currentCallSid || undefined
});
}
async toggleSpeaker() {
this.isSpeakerOn = !this.isSpeakerOn;
await TwilioVoice.setSpeaker({ enabled: this.isSpeakerOn });
}
async sendDTMF(digits: string) {
if (this.currentCallSid) {
await TwilioVoice.sendDigits({
digits,
callSid: this.currentCallSid
});
}
}
async logout() {
await TwilioVoice.logout();
}
private async showIncomingCallDialog(from: string): Promise<boolean> {
// Show native dialog or custom UI
return confirm(`Incoming call from ${from}`);
}
private showCallScreen() {
// Show call UI
console.log('Showing call screen');
}
private hideCallScreen() {
// Hide call UI
console.log('Hiding call screen');
}
private showQualityIndicator(warning: string) {
// Show quality warning
console.log('Quality warning:', warning);
}
}
// Usage
const voiceService = new VoiceCallService();
// Initialize with access token from your backend
const token = await fetchTwilioToken();
await voiceService.initialize(token);
// Make a call
await voiceService.makeCall('+1234567890');
// Control call
await voiceService.toggleMute();
await voiceService.toggleSpeaker();
await voiceService.sendDTMF('1');
// End call
await voiceService.endCall();
// Logout
await voiceService.logout();
  • Fetch access tokens from your backend server, never embed them in the app
  • Implement token refresh logic for long-running sessions
  • Handle network interruptions with reconnection logic
  • Provide visual feedback for call states
  • Test on actual devices with push notifications
  • Implement proper error handling
  • Clean up listeners when components unmount
  • Request microphone permissions before making calls
  • Never store Twilio credentials in the app
  • Generate access tokens on your backend
  • Implement token expiration and refresh
  • Use HTTPS for all token requests
  • Validate incoming calls server-side
  • Verify access token is valid and not expired
  • Check network connectivity
  • Ensure microphone permissions are granted
  • Verify Twilio account is properly configured
  • Verify PushKit/FCM certificates are configured in Twilio
  • Check device is registered for push notifications
  • Test with production certificates
  • Check microphone permissions
  • Verify speaker/bluetooth settings
  • Test audio routing on actual devices

Caller Name Display (CapacitorTwilioCallerName)

Section titled “Caller Name Display (CapacitorTwilioCallerName)”

By default, incoming calls display the caller’s phone number or client ID. You can customize this by passing a CapacitorTwilioCallerName parameter from your TwiML backend to display a friendly name instead.

When generating your TwiML response for the <Client> dial, add the CapacitorTwilioCallerName parameter:

// Java example
Parameter callerNameParam = new Parameter.Builder()
.name("CapacitorTwilioCallerName")
.value("John Doe")
.build();
Client client = new Client.Builder(identity)
.parameter(callerNameParam)
.build();
Dial dial = new Dial.Builder()
.client(client)
.build();
// Node.js example
const VoiceResponse = require('twilio').twiml.VoiceResponse;
const response = new VoiceResponse();
const dial = response.dial();
dial.client({
name: 'CapacitorTwilioCallerName',
value: 'John Doe'
}, identity);
  1. When your backend receives an incoming call, it generates TwiML to route the call
  2. Include the CapacitorTwilioCallerName parameter with the caller’s display name
  3. The plugin automatically extracts this parameter and uses it for:
    • iOS CallKit incoming call screen
    • Android incoming call notification
    • The from field in incomingCall events
    • The pendingInvites array in call status

If CapacitorTwilioCallerName is not provided, the plugin falls back to the caller’s phone number or client ID.