Getting Started
Installation
Section titled “Installation”npm install @capgo/capacitor-twilio-voicenpx cap syncyarn add @capgo/capacitor-twilio-voicenpx cap syncpnpm add @capgo/capacitor-twilio-voicenpx cap syncbun add @capgo/capacitor-twilio-voicenpx cap syncPrerequisites
Section titled “Prerequisites”You’ll need a Twilio account and access tokens for authentication. Sign up at Twilio if you don’t have an account.
Platform Configuration
Section titled “Platform Configuration”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:
- Enable Push Notifications in Xcode capabilities
- Add VoIP background mode
- Configure VoIP certificate in Twilio console
Android
Section titled “Android”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:
- Add
google-services.jsonto your Android project - Configure FCM in Twilio console
Usage Example
Section titled “Usage Example”import { TwilioVoice } from '@capgo/capacitor-twilio-voice';
// Authenticate with Twilioawait TwilioVoice.login({ accessToken: 'your-twilio-access-token'});
// Make a callawait TwilioVoice.makeCall({ to: '+1234567890'});
// Listen for call eventsTwilioVoice.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 callawait TwilioVoice.muteCall({ muted: true, callSid: 'current-call-sid'});
// Toggle speakerawait TwilioVoice.setSpeaker({ enabled: true});
// End callawait TwilioVoice.endCall({ callSid: 'current-call-sid'});
// Logoutawait TwilioVoice.logout();API Reference
Section titled “API Reference”login(options)
Section titled “login(options)”login(options: { accessToken: string }) => Promise<void>Authenticate with Twilio using an access token.
| Param | Type |
|---|---|
options | { accessToken: string } |
logout()
Section titled “logout()”logout() => Promise<void>End user session and clear call state.
isLoggedIn()
Section titled “isLoggedIn()”isLoggedIn() => Promise<{ loggedIn: boolean }>Check current authentication status.
Returns: Promise<{ loggedIn: boolean }>
makeCall(options)
Section titled “makeCall(options)”makeCall(options: { to: string; params?: Record<string, string> }) => Promise<{ callSid: string }>Initiate an outgoing call to a specified number.
| Param | Type |
|---|---|
options | { to: string; params?: Record<string, string> } |
Returns: Promise<{ callSid: string }>
acceptCall(options)
Section titled “acceptCall(options)”acceptCall(options: { callSid: string }) => Promise<void>Accept an incoming call.
| Param | Type |
|---|---|
options | { callSid: string } |
rejectCall(options)
Section titled “rejectCall(options)”rejectCall(options: { callSid: string }) => Promise<void>Reject an incoming call.
| Param | Type |
|---|---|
options | { callSid: string } |
endCall(options?)
Section titled “endCall(options?)”endCall(options?: { callSid?: string }) => Promise<void>Terminate an active call.
| Param | Type |
|---|---|
options | { callSid?: string } (optional) |
muteCall(options)
Section titled “muteCall(options)”muteCall(options: { muted: boolean; callSid?: string }) => Promise<void>Mute or unmute call audio.
| Param | Type |
|---|---|
options | { muted: boolean; callSid?: string } |
setSpeaker(options)
Section titled “setSpeaker(options)”setSpeaker(options: { enabled: boolean }) => Promise<void>Toggle speaker output.
| Param | Type |
|---|---|
options | { enabled: boolean } |
sendDigits(options)
Section titled “sendDigits(options)”sendDigits(options: { digits: string; callSid?: string }) => Promise<void>Send DTMF tones during a call.
| Param | Type |
|---|---|
options | { digits: string; callSid?: string } |
Event Listeners
Section titled “Event Listeners”Available Events
Section titled “Available Events”registered- Successfully registered with Twiliounregistered- Unregistered from TwilioregistrationFailed- Registration failedincomingCall- Incoming call receivedcallConnected- Call successfully connectedcallDisconnected- Call disconnectedcallRinging- Outgoing call is ringingcallReconnecting- Call is reconnectingcallReconnected- Call reconnected after interruptionqualityWarning- Call quality warningerror- An error occurred
Event Examples
Section titled “Event Examples”// Handle incoming callsTwilioVoice.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 changesTwilioVoice.addListener('callConnected', (data) => { console.log('Call connected:', data.callSid); startCallTimer();});
TwilioVoice.addListener('callDisconnected', (data) => { console.log('Call ended:', data.callSid); stopCallTimer(); hideCallScreen();});
// Handle quality warningsTwilioVoice.addListener('qualityWarning', (data) => { console.warn('Call quality warning:', data.warning); showQualityWarning(data.warning);});
// Handle errorsTwilioVoice.addListener('error', (error) => { console.error('Twilio error:', error.message); handleError(error);});
// Remove listeners when doneconst listener = await TwilioVoice.addListener('callConnected', (data) => { console.log('Connected');});
// Later...listener.remove();Complete Example
Section titled “Complete Example”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); }}
// Usageconst voiceService = new VoiceCallService();
// Initialize with access token from your backendconst token = await fetchTwilioToken();await voiceService.initialize(token);
// Make a callawait voiceService.makeCall('+1234567890');
// Control callawait voiceService.toggleMute();await voiceService.toggleSpeaker();await voiceService.sendDTMF('1');
// End callawait voiceService.endCall();
// Logoutawait voiceService.logout();Best Practices
Section titled “Best Practices”- 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
Security Considerations
Section titled “Security Considerations”- 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
Troubleshooting
Section titled “Troubleshooting”Calls Not Connecting
Section titled “Calls Not Connecting”- Verify access token is valid and not expired
- Check network connectivity
- Ensure microphone permissions are granted
- Verify Twilio account is properly configured
Push Notifications Not Working
Section titled “Push Notifications Not Working”- Verify PushKit/FCM certificates are configured in Twilio
- Check device is registered for push notifications
- Test with production certificates
Audio Issues
Section titled “Audio Issues”- 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.
Backend Setup
Section titled “Backend Setup”When generating your TwiML response for the <Client> dial, add the CapacitorTwilioCallerName parameter:
// Java exampleParameter 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 exampleconst VoiceResponse = require('twilio').twiml.VoiceResponse;
const response = new VoiceResponse();const dial = response.dial();dial.client({ name: 'CapacitorTwilioCallerName', value: 'John Doe'}, identity);How It Works
Section titled “How It Works”- When your backend receives an incoming call, it generates TwiML to route the call
- Include the
CapacitorTwilioCallerNameparameter with the caller’s display name - The plugin automatically extracts this parameter and uses it for:
- iOS CallKit incoming call screen
- Android incoming call notification
- The
fromfield inincomingCallevents - The
pendingInvitesarray in call status
If CapacitorTwilioCallerName is not provided, the plugin falls back to the caller’s phone number or client ID.