This repository contains comprehensive examples demonstrating how to quickly integrate the Webex Web Calling SDK into your web applications. The samples showcase voice calling capabilities including device registration, call management, and audio handling using the latest Webex JavaScript SDK.
✅ Two Integration Methods: CDN and NPM package implementations
✅ Voice Calling: Place and receive voice calls through Webex
✅ Device Registration: Register and manage calling devices
✅ Audio Management: Handle local and remote audio streams
✅ Real-time Events: Listen to call states and events
✅ Ready-to-Use: Complete working examples with minimal setup
- Softphone Applications: Build web-based calling interfaces
- Customer Support Tools: Integrate calling into support platforms
- Unified Communications: Add voice calling to existing web apps
- Contact Center Solutions: Build browser-based agent tools
- VoIP Applications: Create web-based communication platforms
- Prerequisites
- Project Structure
- Quick Start
- Implementation Methods
- Configuration
- Usage Guide
- API Reference
- Audio Handling
- Event Management
- Troubleshooting
- Best Practices
- Contributing
- License
- Webex Developer Account - Sign up for free
- Webex Access Token - Generated from Developer Portal or OAuth flow
- Webex Calling License - Required for voice calling functionality
- Modern Web Browser with WebRTC support:
- Chrome 70+ (recommended)
- Firefox 65+
- Safari 12+
- Edge 79+
- Web Server - For serving files locally (included in NPM sample)
- HTTPS Support - Required for microphone access in production
- Webex Calling Organization - Must have Webex Calling enabled
- User License - User must have Webex Calling license assigned
- Device Registration - Calling device must be registered
web-calling-sdk-samples/
├── cdn/ # CDN-based implementation
│ ├── index.html # Complete HTML interface
│ └── app.js # JavaScript implementation
├── npm/ # NPM package implementation
│ ├── package.json # Dependencies and scripts
│ ├── webpack.config.js # Webpack configuration
│ └── src/
│ ├── index.html # HTML interface
│ └── index.js # ES6 module implementation
├── LICENSE # Cisco Sample Code License
└── README.md # This documentation
graph TB
A[Web Application] --> B[Webex Calling SDK]
B --> C[Authentication]
B --> D[Device Registration]
B --> E[Call Management]
C --> F[Access Token]
D --> G[Line Registration]
E --> H[Audio Streams]
F --> I[Webex APIs]
G --> I
H --> J[WebRTC]
I --> K[Webex Cloud]
J --> L[Browser Media APIs]
# Navigate to CDN folder
cd cdn
# Open index.html in a web server
# For quick testing with Python:
python3 -m http.server 8000
# Then visit: http://localhost:8000# Navigate to NPM folder
cd npm
# Install dependencies
npm install
# Build the application
npm run build
# Start the development server
npm start
# Then visit: http://localhost:1234The CDN approach loads the Webex Calling SDK directly from a CDN, making it ideal for rapid prototyping and simple integrations.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>@webex/calling Quickstart</title>
</head>
<body>
<!-- Authentication Section -->
<section id="authentication">
<h2>@webex/calling Quickstart</h2>
<fieldset>
<legend>Credentials</legend>
<input id="access-token" placeholder="Access Token" type="text" required>
<button id="init" onclick="init()">Initialize Calling</button>
<pre id="access-token-status">Not initialized</pre>
</fieldset>
<!-- Device Registration -->
<fieldset>
<legend>Authorization with Webex Calling</legend>
<button id="authenticate_device" onclick="authorize()">Authorize device</button>
<pre id="registration-status">Unregistered</pre>
<button id="deregister_device" onclick="deregister()">Delete Device</button>
</fieldset>
</section>
<!-- Call Management -->
<section id="calls">
<fieldset>
<legend>Calls</legend>
<input id="destination-number" placeholder="Destination Number" type="text" required>
<button id="call" onclick="makeCall()">Place call</button>
<pre id="call-details">No Call</pre>
<button id="call" onclick="endCall()">End call</button>
</fieldset>
</section>
<!-- Audio Elements -->
<audio id="local-audio" muted="true" autoplay></audio>
<audio id="remote-audio" autoplay></audio>
<!-- SDK Loading -->
<script src="https://unpkg.com/webex@2.59.8-next.10/umd/calling.min.js"></script>
<script src="app.js"></script>
</body>
</html>/* global Calling */
// DOM element references
const accessToken = document.querySelector('#access-token');
const authStatusElm = document.querySelector('#access-token-status');
const localAudioElem = document.querySelector('#local-audio');
const remoteAudioElem = document.querySelector('#remote-audio');
const callDetailsElm = document.querySelector('#call-details');
const registerStatus = document.querySelector('#registration-status');
// Global variables
let calling;
let line;
let localAudioStream;
let call;
async function init() {
// Webex SDK configuration
const webexConfig = {
config: {
logger: { level: 'debug' },
meetings: {
reconnection: { enabled: true },
enableRtx: true,
},
encryption: {
kmsInitialTimeout: 8000,
kmsMaxTimeout: 40000,
batcherMaxCalls: 30,
caroots: null,
},
dss: {},
},
credentials: {
access_token: accessToken.value,
}
};
// Calling-specific configuration
const callingConfig = {
clientConfig: {
calling: true,
contact: true,
callHistory: true,
callSettings: true,
voicemail: true,
},
callingClientConfig: {
logger: { level: 'info' }
},
logger: { level: 'info' }
};
// Initialize the calling SDK
calling = await Calling.init({webexConfig, callingConfig});
authStatusElm.innerHTML = 'Initializing...';
calling.on("ready", () => {
calling.register().then(() => {
callingClient = calling.callingClient;
authStatusElm.innerHTML = 'Ready';
});
});
}The NPM approach uses the Webex SDK as a dependency, providing better dependency management and build optimization for production applications.
{
"name": "calling-dummy",
"version": "1.0.0",
"description": "Webex Calling SDK NPM implementation",
"main": "index.js",
"license": "MIT",
"dependencies": {
"webex": "2.59.8-next.10",
"http-server": "^14.1.1"
},
"devDependencies": {
"webpack": "^5.88.2",
"webpack-cli": "^5.1.4"
},
"scripts": {
"start": "http-server ./src -p 1234",
"build": "webpack"
}
}const webpack = require("webpack");
const path = require("path");
module.exports = {
mode: "development",
entry: "./src/index.js",
output: {
filename: "main.js",
path: path.resolve(__dirname, "./src/dist"),
},
resolve: {
fallback: {
http: require.resolve("stream-http"),
https: require.resolve("https-browserify"),
crypto: require.resolve("crypto-browserify"),
stream: require.resolve("stream-browserify"),
os: require.resolve("os-browserify/browser"),
url: require.resolve("url"),
assert: require.resolve("assert"),
fs: false,
querystring: require.resolve("querystring-es3"),
},
},
plugins: [
new webpack.ProvidePlugin({
process: "process/browser",
}),
],
};import Calling from "webex/calling";
let callingClient;
let line;
let calling;
// Configuration objects
const webexConfig = {
config: {
logger: { level: "debug" },
meetings: {
reconnection: { enabled: true },
enableRtx: true,
},
encryption: {
kmsInitialTimeout: 8000,
kmsMaxTimeout: 40000,
batcherMaxCalls: 30,
caroots: null,
},
dss: {},
},
credentials: {
access_token: "YOUR_ACCESS_TOKEN_HERE",
},
};
const callingConfig = {
clientConfig: {
calling: true,
contact: true,
callHistory: true,
callSettings: true,
voicemail: true,
},
callingClientConfig: {
logger: { level: "info" },
},
logger: { level: "info" },
};
// Initialize calling functionality
async function callingInit() {
calling = await Calling.init({ webexConfig, callingConfig });
calling.on("ready", () => {
calling.register().then(() => {
callingClient = calling.callingClient;
});
});
}
// Register calling line
async function registerLine() {
line = Object.values(callingClient.getLines())[0];
line.on("registered", (lineInfo) => {
console.log("Line information: ", lineInfo);
});
line.register();
}
// Export functions to global scope for HTML access
window.registerLine = registerLine;
window.callingInit = callingInit;| Parameter | Description | Example |
|---|---|---|
access_token |
Valid Webex access token | Bearer eyJ0eXAiOiJKV1Q... |
logger.level |
Logging verbosity | 'debug', 'info', 'warn', 'error' |
clientConfig.calling |
Enable calling functionality | true |
clientConfig.contact |
Enable contact management | true |
clientConfig.callHistory |
Enable call history | true |
const webexConfig = {
config: {
meetings: {
reconnection: { enabled: true },
enableRtx: true, // Enable retransmissions
enableExtmap: true, // Enable extension mapping
},
encryption: {
kmsInitialTimeout: 8000, // Key management timeout
kmsMaxTimeout: 40000, // Maximum KMS timeout
batcherMaxCalls: 30, // Batch call limit
}
}
};const audioConfig = {
constraints: {
audio: {
echoCancellation: true,
noiseSuppression: true,
autoGainControl: true,
sampleRate: 48000,
channelCount: 1
}
}
};// Set up configuration
const webexConfig = { /* configuration */ };
const callingConfig = { /* calling config */ };
// Initialize calling
const calling = await Calling.init({webexConfig, callingConfig});
// Wait for ready state
calling.on("ready", () => {
console.log("Calling SDK is ready");
});// Register the calling client
await calling.register();
// Get the first available line
const line = Object.values(calling.callingClient.getLines())[0];
// Listen for registration events
line.on("registered", (lineInfo) => {
console.log("Device registered:", lineInfo);
});
// Register the line
line.register();// Create microphone stream
const localAudioStream = await Calling.createMicrophoneStream({audio: true});
// Attach to local audio element
document.querySelector('#local-audio').srcObject = localAudioStream.outputStream;// Create a call
const call = line.makeCall({
type: 'uri',
address: 'user@example.com' // or phone number
});
// Handle call events
call.on('progress', (correlationId) => {
console.log('Call in progress');
});
call.on('established', (correlationId) => {
console.log('Call established');
});
call.on('remote_media', (track) => {
// Handle remote audio
const remoteAudio = document.querySelector('#remote-audio');
remoteAudio.srcObject = new MediaStream([track]);
});
// Dial the call
await call.dial(localAudioStream);// End the active call
call.end();
// Clean up audio streams
localAudioStream.stop();| Method | Description | Parameters | Returns |
|---|---|---|---|
Calling.init() |
Initialize the calling SDK | {webexConfig, callingConfig} |
Promise<Calling> |
calling.register() |
Register the calling client | None | Promise<void> |
calling.callingClient |
Get the calling client instance | None | CallingClient |
| Method | Description | Parameters | Returns |
|---|---|---|---|
getLines() |
Get available calling lines | None | Object<Line> |
getCall(callId) |
Get call by ID | callId: string |
Call |
getActiveCalls() |
Get all active calls | None | Array<Call> |
| Method | Description | Parameters | Returns |
|---|---|---|---|
register() |
Register the line | None | Promise<void> |
deregister() |
Deregister the line | None | Promise<void> |
makeCall(options) |
Create a new call | {type, address} |
Call |
| Method | Description | Parameters | Returns |
|---|---|---|---|
dial(stream) |
Dial the call | MediaStream |
Promise<void> |
answer(stream) |
Answer incoming call | MediaStream |
Promise<void> |
end() |
End the call | None | Promise<void> |
hold() |
Put call on hold | None | Promise<void> |
resume() |
Resume held call | None | Promise<void> |
Creates a microphone audio stream for calling.
const stream = await Calling.createMicrophoneStream({
audio: {
echoCancellation: true,
noiseSuppression: true,
autoGainControl: true
}
});Parameters:
constraints(Object): Media constraints for audio capture
Returns:
Promise<LocalAudioStream>: Audio stream object
graph LR
A[Microphone] --> B[LocalAudioStream]
B --> C[Call.dial()]
D[Remote Audio] --> E[MediaTrack]
E --> F[Audio Element]
G[Local Audio Element] --> B
G --> H[Muted for Feedback Prevention]
// Create and configure local audio stream
async function setupLocalAudio() {
const localStream = await Calling.createMicrophoneStream({
audio: {
echoCancellation: true,
noiseSuppression: true,
autoGainControl: true,
sampleRate: 48000
}
});
// Attach to audio element (muted to prevent feedback)
const localAudio = document.querySelector('#local-audio');
localAudio.srcObject = localStream.outputStream;
localAudio.muted = true; // Important: prevent audio feedback
return localStream;
}// Handle incoming remote audio
call.on('remote_media', (track) => {
console.log('Received remote media track:', track);
// Create media stream from track
const remoteStream = new MediaStream([track]);
// Attach to remote audio element
const remoteAudio = document.querySelector('#remote-audio');
remoteAudio.srcObject = remoteStream;
remoteAudio.autoplay = true;
});// Configure audio constraints for optimal quality
const audioConstraints = {
audio: {
echoCancellation: true, // Remove echo
noiseSuppression: true, // Reduce background noise
autoGainControl: true, // Automatic volume adjustment
sampleRate: 48000, // High quality sample rate
channelCount: 1, // Mono audio for calls
latency: 0.02, // Low latency (20ms)
volume: 1.0 // Full volume
}
};// Complete call event handling
call.on('caller_id', (callerIdInfo) => {
console.log('Caller ID:', callerIdInfo.callerId);
// Update UI with caller information
});
call.on('progress', (correlationId) => {
console.log('Call progress:', correlationId);
// Show "calling..." state
});
call.on('connect', (correlationId) => {
console.log('Call connected:', correlationId);
// Show "connected" state
});
call.on('established', (correlationId) => {
console.log('Call established:', correlationId);
// Show "active call" state
// Enable call controls (mute, hold, etc.)
});
call.on('disconnect', (correlationId) => {
console.log('Call disconnected:', correlationId);
// Clean up UI and audio streams
// Reset call state
});
call.on('remote_media', (track) => {
console.log('Remote media received:', track);
// Handle incoming audio/video
});// Line registration events
line.on('registered', (lineInfo) => {
console.log('Line registered successfully');
console.log('Device ID:', lineInfo.mobiusDeviceId);
console.log('User ID:', lineInfo.userId);
console.log('SIP Address:', lineInfo.sipAddresses[0]);
});
line.on('unregistered', (reason) => {
console.log('Line unregistered:', reason);
});
line.on('incoming_call', (call) => {
console.log('Incoming call received:', call);
// Handle incoming call UI
// Show answer/decline options
});// Calling client events
calling.on('ready', () => {
console.log('Calling client is ready');
});
calling.on('error', (error) => {
console.error('Calling client error:', error);
});Problem: 401 Unauthorized errors during initialization.
Solutions:
- Verify access token is valid and not expired
- Ensure token has calling scopes
- Check that user has Webex Calling license
// Debug authentication
console.log('Access token:', accessToken.substring(0, 20) + '...');Problem: Line registration fails or times out.
Solutions:
- Verify user has Webex Calling license
- Check network connectivity
- Ensure organization has Webex Calling enabled
// Debug registration
line.on('registration_failed', (error) => {
console.error('Registration failed:', error);
});Problem: No audio during calls or microphone not working.
Solutions:
- Check browser permissions for microphone access
- Verify HTTPS is used (required for getUserMedia)
- Test microphone with other applications
// Test microphone access
navigator.mediaDevices.getUserMedia({audio: true})
.then(stream => console.log('Microphone access granted'))
.catch(error => console.error('Microphone access denied:', error));Problem: Calls fail to connect or have poor quality.
Solutions:
- Check network firewall settings
- Verify WebRTC is supported in browser
- Test with different browsers
// Debug call states
call.on('connect', () => console.log('Call connected'));
call.on('disconnect', () => console.log('Call disconnected'));// Enable comprehensive debugging
const debugConfig = {
config: {
logger: {
level: 'debug',
historyLength: 1000
}
},
callingClientConfig: {
logger: {
level: 'debug'
}
}
};// Check WebRTC support
function checkWebRTCSupport() {
const isSupported = !!(
navigator.getUserMedia ||
navigator.webkitGetUserMedia ||
navigator.mozGetUserMedia ||
navigator.msGetUserMedia ||
navigator.mediaDevices?.getUserMedia
);
console.log('WebRTC supported:', isSupported);
return isSupported;
}-
Token Management:
// ❌ Don't store tokens in localStorage localStorage.setItem('token', accessToken); // ✅ Use secure, httpOnly cookies or server-side storage // Store tokens server-side and use session authentication
-
HTTPS Requirement:
// ✅ Always use HTTPS in production if (location.protocol !== 'https:' && location.hostname !== 'localhost') { console.warn('HTTPS required for microphone access'); }
-
Resource Cleanup:
// ✅ Clean up resources when done function cleanupCall() { if (localAudioStream) { localAudioStream.stop(); } if (call) { call.end(); } } // Clean up on page unload window.addEventListener('beforeunload', cleanupCall);
-
Event Listener Management:
// ✅ Remove event listeners to prevent memory leaks function removeCallListeners() { call.off('progress'); call.off('established'); call.off('disconnect'); }
-
Loading States:
// ✅ Show loading states during initialization function showLoadingState(message) { document.querySelector('#status').textContent = message; } showLoadingState('Initializing calling...');
-
Error Handling:
// ✅ Provide user-friendly error messages function handleCallError(error) { const userMessage = getErrorMessage(error); showUserNotification(userMessage, 'error'); }
-
Environment Configuration:
// Use environment-specific configuration const config = { development: { logger: { level: 'debug' }, apiUrl: 'https://api-dev.webex.com' }, production: { logger: { level: 'error' }, apiUrl: 'https://api.webex.com' } };
-
Error Monitoring:
// Implement error tracking calling.on('error', (error) => { // Send to error tracking service errorTracker.captureException(error); });
We welcome contributions to improve these sample implementations!
- Fork the repository
- Create a feature branch:
git checkout -b feature/your-improvement
- Test your changes:
- Test both CDN and NPM implementations
- Verify functionality across different browsers
- Test with various calling scenarios
- Submit a pull request
- Code Style: Follow JavaScript ES6+ best practices
- Documentation: Update README for new features
- Testing: Test with real Webex calling environment
- Browser Support: Ensure compatibility with supported browsers
-
Clone the repository:
git clone <repository-url> cd web-calling-sdk-samples
-
Test CDN implementation:
cd cdn python3 -m http.server 8000 -
Test NPM implementation:
cd npm npm install npm run build npm start
This project is licensed under the Cisco Sample Code License.
- ✅ Permitted: Copy, modify, and redistribute for use with Cisco products
- ❌ Prohibited: Use independent of Cisco products or to compete with Cisco
- ℹ️ Warranty: Provided "as is" without warranty
- ℹ️ Support: Not supported by Cisco TAC
See the LICENSE file for full license terms.
- Webex Developer Portal
- Webex JavaScript SDK Documentation
- Webex Calling API Reference
- WebRTC API Documentation
Start Building Amazing Calling Experiences! 🚀📞