>
Welcome to the Webex Contact Center SDK Lab! This lab demonstrates how to build applications using the
Webex JavaScript SDK. For simplicity,
we're using a CDN version of the SDK in this lab, but in production applications, you would typically
install
it via npm (@webex/plugin-cc
Will be renamed to @webex/contact-center soon).
Important Prerequisite: Before starting this lab, ensure your Webex Contact Center is set up and configured. For detailed instructions on setting up your Contact Center environment, please refer to the official documentation at https://webexcc.github.io/ , Please make sure Lab 0 to Lab 3 is finished , we would need agent credentials and the entry point phone number to dial .
Follow the steps below to learn how to authenticate with the SDK, register agents, handle interactions, and manage contact center tasks using the SDK's Contact Center capabilities.
The first step is to initialize the Webex SDK. This can be done using a temporary Personal Access Token
(PAT) for development or via OAuth for production applications.
The initialized webex
object is crucial as it's used for all subsequent SDK interactions.
This lab's index.js
and auth.js
handle the initialization logic.
Using a Personal Access Token:
// How it works (simplified from auth.js and index.js):
// 1. User enters token in the input field and clicks "Initialize with Token".
// 2. The token string is retrieved from the input field.
const accessToken = document.getElementById('access-token').value;
// 3. The Webex SDK is initialized with this token.
const webex = Webex.init({
credentials: {
access_token: accessToken // Value from the input field
}
});
// 4. Wait for the SDK to be fully initialized
await new Promise((resolve) => {
webex.once('ready', () => {
console.log('Webex SDK initialization complete');
resolve();
});
});
// 5. The 'webex' object is stored globally (e.g., window.webex) after successful initialization.
// This 'webex' instance is then used for all other SDK calls (register, stationLogin, etc.).
// index.js calls initWithAccessToken(token) from auth.js which returns the initialized webex object.
Using OAuth (for production):
To use OAuth, you first need to create an Integration on the Webex Developer Portal.
During the integration creation process, you will be provided with a Client ID
. You will also
need to specify a Redirect URI
.
This Redirect URI
is the URL that Webex will redirect the user back to after they have
successfully authenticated. For this lab, if you are running it locally,
this would typically be http://localhost:
where
is the port number
your local server is running on (e.g., http://localhost:8000/ if you started a simple server on port 8000).
Make sure this exact Redirect URI is added to your integration settings on the Webex Developer Portal.
// How it works (simplified from auth.js and index.js):
// 1. User clicks "Login via OAuth".
// 2. SDK is initialized with OAuth 2.0 parameters (client_id, redirect_uri, scopes).
// - client_id: Obtain this from your Webex Integration on developer.webex.com.
// - redirect_uri: This must match one of the Redirect URIs configured in your Webex Integration.
const webex = Webex.init({
config: {
credentials: {
client_id: 'YOUR_PUBLIC_CLIENT_ID', // Provided by your Webex integration (developer.webex.com/my-apps)
redirect_uri: 'YOUR_APP_REDIRECT_URI', // Your app's callback URL (must be registered in the integration)
// Required Contact Center and Calling scopes:
scope: [
// Contact Center mandatory scopes
"cjp:config_read", // Read configuration
"cjp:config_write", // Modify configuration
"cjp:config", // General configuration access
"cjp:user", // User operations
// WebRTC Calling scopes
"spark:webrtc_calling", // Browser calling
"spark:calls_read", // Read call data
"spark:calls_write", // Modify call state
"spark:xsi", // Extended services
// Additional scopes
"spark:kms" // Required for encryption
].join(' ')
}
}
});
// 3. The OAuth 2.0 login flow is initiated.
await webex.authorization.initiateLogin(); // This redirects the user to Webex login.
// 4. After successful login and redirect back to your app:
// - The SDK automatically exchanges the authorization code for an access token
// - Once complete, the SDK emits the 'ready' event
await new Promise((resolve) => {
webex.once('ready', () => {
console.log('Webex SDK initialized with OAuth token');
resolve();
});
});
// 5. The 'webex' object is now ready for use and stored globally.
Output of this step: An initialized webex
SDK object (often stored as
window.webex
), ready for Contact Center operations.
Relevant JS function: initWithAccessToken
in auth.js
Once the SDK is authenticated (webex
object is available), the agent needs to register with the
Contact Center.
The webex.cc.register()
method is called.
The response from this call, often called agentProfile
, contains vital information used to
populate UI elements for subsequent steps (like selecting a team, login method, or agent state).
This lab's registration.js
handles this and populates the dropdowns.
// How it works (simplified from registration.js and index.js):
// Input: The initialized 'webex' object from Step 1.
async function register(webex) { // 'webex' is window.webex from Step 1
try {
const agentProfile = await webex.cc.register();
// 'agentProfile' is the successful response from the SDK.
console.log('Agent registered. Profile:', agentProfile);
// Key values from agentProfile and how they are used:
const teams = agentProfile.teams; // Used to populate the "Select Team" dropdown (id="teamsDropdown")
const loginVoiceOptions = agentProfile.loginVoiceOptions; // Used to populate the "Agent Login" dropdown (id="AgentLogin")
const agentStates = agentProfile.agentStates; // Used to populate the "Choose State" dropdown (id="sel-state")
const idleCodes = agentProfile.idleCodes; // Used to populate the "Choose State" dropdown (id="sel-state") for Idle/Aux codes
const wrapupCodes = agentProfile.wrapupCodes; // Used to populate the "Choose Wrapup Code" dropdown (id="wrapup-codes") for Step 7
// Example of populating teams (done in registration.js -> populateTeamsDropdown):
const teamsDropdown = document.getElementById('teamsDropdown');
teams.forEach(team => {
const option = document.createElement('option');
option.value = team.id;
option.text = team.name;
teamsDropdown.add(option);
});
return agentProfile; // This profile is stored or used by other functions.
} catch (error) {
console.error('Registration failed:', error);
throw error;
}
}
// To deregister:
await window.webex.cc.deregister();
Output of this step: An agentProfile
object containing agent details, teams,
available states, login options, and wrapup codes. These values are used to configure the UI for the next
steps.
Note: Contact Center SDK automatically handles reconnection and session management.
Relevant JS function: registerAgent
in registration.js
After successful registration, the agent logs into a station. This makes them available to handle
interactions for a specific team and using a chosen device (Browser/WebRTC, Agent DN, or Extension).
The webex.cc.stationLogin()
method is used.
Inputs for this step come from UI selections (team, login method, dial number) which were populated based on
agentProfile
from Step 2.
This lab's station-login.js
handles this logic.
// How it works (simplified from station-login.js and index.js):
// Inputs:
// - 'webex' object (from Step 1).
// - 'teamId': Selected value from the "Select Team" dropdown (id="teamsDropdown").
// This dropdown was populated using agentProfile.teams from Step 2.
// - 'loginOption': Selected value from the "Agent Login" dropdown (id="AgentLogin").
// This dropdown was populated using agentProfile.loginVoiceOptions from Step 2.
// - 'dialNumber': Value from the "Dial Number" input field (id="dialNumber"), if loginOption is AGENT_DN or EXTENSION.
async function loginToStation(webex, selectedTeamId, selectedLoginOption, inputDialNumber) {
try {
const stationLoginPayload = {
teamId: selectedTeamId, // e.g., "Y2lzY29zcGFyazovL3VzL1RFQU0vMWMyYj..."
loginOption: selectedLoginOption // e.g., "BROWSER", "AGENT_DN", "EXTENSION"
};
if (selectedLoginOption === 'AGENT_DN' || selectedLoginOption === 'EXTENSION') {
stationLoginPayload.dialNumber = inputDialNumber; // e.g., "+15551234567" or "1001"
}
const response = await webex.cc.stationLogin(stationLoginPayload);
console.log('Station login successful:', response);
// 'response' contains details like 'deviceId', which is important for logout.
// window.deviceId = response.deviceId; // Stored for later use (e.g., in stationLogout)
// After successful station login, the agent is typically ready to change their state (Step 4)
// and receive tasks (Step 5).
return response;
} catch (error) {
console.error('Station login failed:', error);
throw error;
}
}
// Optionally, you can listen for station login events:
webex.cc.on('agent:stationLoginSuccess', (eventData) => {
console.log('Station login successful via event:', eventData);
});
webex.cc.on('agent:stationLoginFailed', (error) => {
console.log('Station login failed via event:', error);
});
// Listen for station logout events:
webex.cc.on('agent:logoutSuccess', (eventData) => {
console.log('Station logout successful via event:', eventData);
});
webex.cc.on('agent:logoutFailed', (error) => {
console.log('Station logout failed via event:', error);
});
// To logout from station:
await window.webex.cc.stationLogout({ logoutReason: 'User Initiated', deviceId: window.deviceId });
Output of this step: Agent is logged into a station. The response includes a
deviceId
. The agent can now manage their state and handle tasks.
Relevant JS function: loginToStation
in station-login.js
Once logged into a station, an agent can manage their availability state (e.g., Available, Idle/NotReady
with an auxiliary code like "Lunch").
The webex.cc.setAgentState()
method is used.
The "Choose State" dropdown (id="sel-state"
) is populated using agentStates
and
auxCodes
from the agentProfile
obtained in Step 2.
The SDK also emits an agent:stateChange
event to confirm state transitions.
This lab's state-change.js
handles this.
// How it works (simplified from state-change.js and index.js):
// Inputs:
// - 'webex' object (from Step 1).
// - 'state': The desired primary state, either 'Available' or 'Idle', and nothing else.
// - 'auxCodeId': The ID of the auxiliary code, especially for "Idle" states.
// Both are derived from the selection in the "Choose State" dropdown (id="sel-state").
// This dropdown was populated using agentProfile.agentStates/idleCodes from Step 2.
async function changeAgentState(webex, targetState, targetAuxCodeId) {
// Example: User selects "Lunch" from the "Choose State" dropdown.
// The selected option might have:
// - data-state="Idle" (or a specific not-ready state name)
// - value="auxCodeIdForLunch" (the ID for the "Lunch" aux code)
try {
const response = await webex.cc.setAgentState({
state: targetState, // e.g., "Idle"
auxCodeId: targetAuxCodeId, // e.g., "auxCodeIdForLunch"
lastStateChangeReason: 'User Initiated'
});
console.log('State set successfully:', response);
// The agent's state is now updated on the backend.
return response;
} catch (error) {
console.error('Failed to set state:', error);
throw error;
}
}
// Listen for success/failure of state change attempts:
webex.cc.on('agent:stateChangeSuccess', (eventData) => {
console.log('State change successful via event:', eventData);
});
webex.cc.on('agent:stateChangeFailed', (error) => {
console.log('State change failed via event:', error);
});
// Listen for any state changes (both local and remote):
webex.cc.on('agent:stateChange', (eventData) => {
console.log('Agent state changed (local or remote):', eventData.state, 'AuxCode:', eventData.auxCodeId);
// This event triggers for both:
// 1. Local state changes made by this agent
// 2. Remote state changes (made by supervisor or system)
// Update UI to reflect eventData.state and eventData.auxCodeId.
// For example, re-select the correct option in the "sel-state" dropdown.
});
Output of this step: The agent's state is changed in the Contact Center system. The
agent:stateChange
event provides confirmation and the new state details.
Note: Some states may require aux codes for additional context.
Relevant JS function: changeAgentState
in state-change.js
When an agent is in the Avaliable state, the Contact Center can offer them tasks (e.g., calls, chats).
The SDK uses event listeners to notify the application about task-related events.
The most important initial event is task:incoming
, which provides a task
object.
This task
object is the key to managing that specific interaction.
This lab's task-manager.js
sets up these listeners.
// How it works (simplified from task-manager.js and index.js):
// Input: The initialized 'webex' object (from Step 1).
function setupTaskEventListeners(webex) { // webex is window.webex
// Event: A new task is being offered to the agent.
webex.cc.on('task:incoming', async (task) => {
// When a new task arrives:
// 1. task object contains everything needed to handle this interaction
console.log('New task received! Interaction ID:', task.data.interactionId);
console.log('Media type:', task.data.interaction.mediaType); // e.g., "telephony", "chat"
// 2. You can directly accept or decline the task:
try {
// To accept the task:
await task.accept();
console.log('Task accepted successfully');
// The task:assigned event will be triggered automatically
// OR to decline the task:
// await task.decline();
// console.log('Task declined successfully');
} catch (error) {
console.error('Failed to handle task:', error);
}
// 3. Listen for task-specific events
setupTaskSpecificEventHandlers(task);
});
// Event: Task state restored after a page refresh or reconnection.
webex.cc.on('task:hydrate', (task) => {
console.log('Task hydrated (restored):', task.data.interactionId);
// Similar to 'task:incoming', this 'task' object represents an active task.
// Store it and set up its specific event handlers.
// window.currentTask = task;
setupTaskSpecificEventHandlers(task);
});
}
// Setting up listeners for events on a specific task instance:
function setupTaskSpecificEventHandlers(currentTask) { // currentTask is the object from 'task:incoming' or 'task:hydrate'
// Event: Task has been successfully assigned to the agent (e.g., after agent accepts).
currentTask.on('task:assigned', (data) => {
console.log('Task assigned to agent:', currentTask.data.interactionId);
// UI can now update to show active task details.
});
// Event: For voice calls, this event provides the audio media stream.
currentTask.on('task:media', (track) => { // 'track' is a MediaStreamTrack
console.log('Media track received for voice call:', currentTask.data.interactionId);
// This 'track' needs to be connected to an HTML
Output of this step: When a task is offered, a task
object is received. This
object is then used for all further actions and event handling related to that specific interaction (see
Step 6 and 7).
Relevant JS functions:
setupTaskEventListeners
in task-manager.js
handleIncomingTask
in task-manager.js
Once a task
object is received (from task:incoming
in Step 5) and potentially
accepted, the agent can perform various actions on it, like accepting, declining, holding, muting, ending,
etc.
All these actions are methods called directly on the task
object.
This lab's task-manager.js
implements these control functions.
// All functions below take the 'task' object (obtained in Step 5) as input.
// Let's assume 'currentTask' holds the task object from the 'task:incoming' event.
// Hold/Resume Controls:
// Each operation returns a promise and also emits events
async function toggleHoldCall(currentTask) {
// First, set up event listeners for hold/resume events
currentTask.on('task:hold', () => {
console.log('Hold event received - call is now on hold');
// Update UI to show hold state
});
currentTask.on('task:unhold', () => {
console.log('Unhold event received - call is now active');
// Update UI to show active state
});
try {
if (currentTask.data.media?.isHold) {
// Promise-based approach
await currentTask.resume();
console.log('Resume command sent successfully');
} else {
// Promise-based approach
await currentTask.hold();
console.log('Hold command sent successfully');
}
} catch (error) {
console.error('Hold/resume operation failed:', error);
}
}
// Recording Controls:
async function toggleCallRecording(currentTask) {
// Set up event listeners for recording state changes
currentTask.on('task:recordingPaused', () => {
console.log('Recording pause event received');
// Update recording UI to paused state
});
currentTask.on('task:recordingPauseFailed', () => {
console.log('Recording pause failed event received');
// Show error in UI
});
currentTask.on('task:recordingResumed', () => {
console.log('Recording resume event received');
// Update recording UI to active state
});
currentTask.on('task:recordingResumeFailed', () => {
console.log('Recording resume failed event received');
// Show error in UI
});
try {
if (currentTask.data.interaction.recording?.paused === false) {
// Promise-based approach
await currentTask.pauseRecording();
console.log('Pause recording command sent successfully');
} else {
// Promise-based approach
await currentTask.resumeRecording();
console.log('Resume recording command sent successfully');
}
} catch (error) {
console.error('Recording operation failed:', error);
}
}
// End Task Controls:
async function endCurrentTask(currentTask) {
try {
// Promise-based approach
await currentTask.end();
console.log('End task command sent successfully');
} catch (error) {
console.error('End task failed:', error);
}
}
// Event: Task has ended
currentTask.on('task:end', () => {
console.log('Task ended:', currentTask.data.interactionId);
// Clean up UI related to this task
});
// Consult and Transfer operations also use the 'currentTask' object:
// await currentTask.consult({ destinationType: 'agent', to: 'agentIdOrExtension', mediaType: 'telephony' });
// await currentTask.transfer({ destinationType: 'queue', to: 'queueId' });
// await currentTask.endConsult({ ... });
// await currentTask.consultTransfer();
Output of these actions: The state of the currentTask
changes within the
Contact Center. Corresponding events (e.g., task:hold
, task:resume
,
task:end
) are emitted on the currentTask
object, allowing the UI to update.
Relevant JS functions:
acceptTask
, endTask
, toggleHold
, toggleMute
,
toggleRecordingPause
in task-manager.js
After a task ends (e.g., a call is disconnected), if wrapUpRequired
was true in the
task:end
event (see Step 5), the agent needs to perform wrapup.
This involves selecting a wrapup code and submitting it using the task.wrapup()
method on the
same task
object that just ended.
The "Choose Wrapup Code" dropdown (id="wrapup-codes"
) was populated in Step 2 using
agentProfile.wrapupCodes
.
This lab's task-manager.js
(specifically submitWrapup
function) handles this.
// How it works (simplified from task-manager.js's submitWrapup):
// Inputs:
// - 'currentTask': The specific task object that has ended and requires wrapup (this is the same 'task' object from Step 5 & 6).
// - 'selectedWrapupCodeId': The ID of the wrapup code selected from the "Choose Wrapup Code" dropdown (id="wrapup-codes").
// This dropdown was populated using agentProfile.wrapupCodes from Step 2.
// First, set up wrap-up event listeners
function setupWrapupEvents(currentTask) {
// Triggered when task enters wrap-up state
currentTask.on('task:wrapup', (task) => {
console.log('Task entered wrap-up state:', task.data.interactionId);
// Enable wrap-up UI
// Show wrap-up form with codes
document.getElementById('wrapup-codes').disabled = false;
document.getElementById('btn-wrapup').disabled = false;
});
// Triggered when wrap-up is completed
currentTask.on('task:wrappedup', (task) => {
console.log('Task wrap-up completed:', task.data.interactionId);
// Clean up wrap-up UI
document.getElementById('wrapup-codes').disabled = true;
document.getElementById('btn-wrapup').disabled = true;
// Agent may return to Available state
});
}
// Then handle the wrap-up operation
async function completeTaskWrapup(currentTask, selectedWrapupCodeId) {
try {
// Promise-based approach to submit wrap-up
await currentTask.wrapup({
auxCodeId: selectedWrapupCodeId, // ID from wrapup-codes dropdown
});
console.log('Wrap-up submitted successfully');
// The task:wrappedup event will be triggered automatically
// Listen for it using the events above to update UI
} catch (error) {
console.error('Wrap-up failed:', error);
throw error;
}
}
// Usage example:
// 1. When task ends, check if wrap-up is required
currentTask.on('task:end', (data) => {
if (data.wrapUpRequired) {
setupWrapupEvents(currentTask);
// task:wrapup event will be triggered by the system
}
});
Output of this step: The task is formally completed with a wrapup reason. The agent is typically ready for new tasks or returns to an idle state.
Relevant JS function: submitWrapup
in task-manager.js
When ending an agent session (e.g., end of shift, application shutdown), it's important to properly clean up by logging out from the station and deregistering from the Contact Center.
// How to properly perform cleanup:
async function cleanup() {
try {
// 1. First, log out from the station
await webex.cc.stationLogout({
logoutReason: 'User Initiated',
deviceId: window.deviceId // Store deviceId from stationLogin response
});
// Station logout events you can listen for:
webex.cc.on('agent:logoutSuccess', (eventData) => {
console.log('Station logout successful via event');
// Clear station-related UI state
});
webex.cc.on('agent:logoutFailed', (error) => {
console.error('Station logout failed:', error);
});
// 2. Then deregister the SDK
await webex.cc.deregister();
console.log('SDK deregistered successfully');
// 3. Clean up application state
// - Clear stored deviceId
// - Reset UI to initial state
// - Clear any stored tasks or agent data
window.deviceId = null;
window.currentTask = null;
} catch (error) {
console.error('Cleanup failed:', error);
throw error;
}
}
When to call cleanup:
Relevant JS function: cleanup
in cleanup.js
// To upload logs for troubleshooting:
await webex.cc.uploadLogs({
description: 'Brief description of the issue'
});
// The response will include a logId that can be referenced in support tickets