Remote manual control of RTMS streams
RTMS for Contact Center allows you to manually control your streams to start, pause, and stop when appropriate. To use this method, the following is required:
- Within your app integration settings, toggle the "auto" switch based on your needs: off to manually start, stop, and pause streams, or on to auto-start streams with the option to manually stop and pause
- An account with admin privileges and an RTMS license
- The
contact_center_rtms:write:adminscope must be added to the application
To control the stream of a specific engagement, make a call to the update engagement RTMS app status endpoint, authorized with your account's bearer token. Construct your request as outlined here:
- The specific engagement ID (must be for an active engagement) is added as a query parameter in your PUT request
- The specified action to be implemented: start, stop, pause, resume
- The client ID of the authorized application you want to control, specified in settings
Constructing an API call
With manual control, admins can manage RTMS streams from separate applications. Using the engagement ID of the selected active engagement, the endpoint can be constructed:
https://api.zoom.us/v2/contact_center/LjjdDBo5SkGneP8qY4SvuQ/rtms_app/status
Make a PUT request to the endpoint. This can be called programmatically or dynamically based on your design. For example, if a stream is designed to end 32 minutes after the call starts, a component might look like this:
async function stopRTMS(engagementId, accessToken) {
const response = await fetch(
`https://api.zoom.us/v2/contact_center/open_engagements?engagement_id=${engagementId}`,
{
headers: { Authorization: `Bearer ${accessToken}` },
},
);
const data = await response.json();
const start_time = data.engagements.start_time;
setTimeout(
() => {
fetch(
`https://api.zoom.us/v2/contact_center/${engagementId}/rtms_app/status`,
{
method: 'PUT',
headers: {
Authorization: `Bearer ${accessToken}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
action: 'stop',
settings: { client_id: 'YOUR_CLIENT_ID' },
}),
},
);
// Calculate: call start time + 32 minutes - current time = time until stop
},
new Date(start_time).getTime() + 32 * 60 * 1000 - Date.now(),
);
}
Admins can also pause and resume a stream using manual control. The example code below shows these actions tied to user interactions, such as an admin pressing a button in a remote application.
let paused = false;
button.onclick = async () => {
paused = !paused;
await fetch(
`https://api.zoom.us/v2/contact_center/${engagementId}/rtms_app/status`,
{
method: 'PUT',
headers: {
Authorization: `Bearer ${accessToken}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
// Add "action" value conditionally, maintaining one button
action: paused ? 'pause' : 'resume',
settings: {
client_id: 'YOUR_CLIENT_ID',
},
}),
},
);
// Render button text based on stream status
button.textContent = paused ? 'Resume RTMS' : 'Pause RTMS';
};
Manual control from Contact Center flows
Manual control also enables you to manage stream status directly from your flow. Using the script widget, you can tie RTMS stream actions, like starting a stream, to specific flow events. Follow this guidance to avoid errors:
- Ensure your application(s) have all the necessary scopes added
- If controlling an externally added application, create a server-to-server (s2s) application in the marketplace with the required scopes configured
- Ensure your application(s) are configured and assigned to the appropriate queue
- If controlling an externally added application, ensure both your s2s application and your installed application are configured properly under the integrations > streaming section of "Contact Center Management", with identical Queue and Flow configurations (if applicable)
- Store your app credentials as global variables
- Use separate variables for your s2s app's client ID and the external app's client ID
Regardless of where you place it in your flow, the script for calling the stream control endpoint follows this general pattern:
// generate an access token with a s2s application
async function generateToken() {
const accountID = var_get()['global_custom.rtms_s2s.accountId'];
// use the client ID for your personal s2s application
const clientID = var_get()['global_custom.**rtms_s2s**.clientId'];
const clientSecret = var_get()['global_custom.rtms_s2s.clientSecret'];
const tokenResponse = await req.post(
`https://zoom.us/oauth/token?grant_type=account_credentials&token_index=0&account_id=${accountID}`,
{},
{
auth: {
username: clientID,
password: clientSecret,
},
},
);
//store your access token
log.debug(tokenResponse.data.access_token);
const token = tokenResponse.data.access_token;
return token;
}
async function main() {
//Get Token
const token = await generateToken();
// Start RTMS
const engagement_id = var_get()['global_system.Engagement.engagementId'];
const request_url = `https://api.zoom.us/v2/contact_center/${engagement_id}/rtms_app/status`;
const start_body = {
action: 'start',
settings: {
// Input the client ID of the installed external app you wish to control
client_id: var_get()['global_custom.**rtms_external**.clientId'],
},
};
const request_config = {
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${token}`,
},
};
const res = await req.put(request_url, start_body, request_config);
const end_body = {
action: 'stop',
settings: {
// Input the client ID of the installed external app you wish to control
client_id: var_get()['global_custom.**rtms_external**.clientId'],
},
};
const res2 = await req.put(request_url, end_body, request_config);
log.debug(res.data);
return res.data;
}
Stream control in start widget
In the start widget (the first widget of your flow), you can add a script and tie it to specific engagement events. This creates an environment where RTMS starts automatically when your engagement begins.
Since this script runs based on a widget event rather than a conditional, you can use the general script pattern above to generate a token and make an authorized call to the endpoint with the appropriate credentials.
Stream control based on user input
You can also tie RTMS stream operations to events during a call, such as a caller's input or response, using the "collect input" widget. In this example, the widget is named "RTMSConsent." The script widget, added as the next step, makes the API call to start RTMS.
In this scenario, a conditional based on consumer input determines whether the script runs. It will only execute if the entered digit (captured using the var_get built-in function) matches the consent digit communicated to the consumer. Below is the customization applied to the "collect input" widget to enable this functionality:
Finally, here's example code showing the request made based on user input, with "start" as the action. As shown on line 2, this function captures consumer input by retrieving the "Digits" value from the "collect input" object (named RTMSConsent in this scenario):
async function startRTMS(engagementId, clientId) {
const token = await generateToken();
const request_url = `https://api.zoom.us/v2/contact_center/${engagementId}/rtms_app/status`;
const body = {
action: 'start',
settings: {
client_id: clientId,
},
};
const request_config = {
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${token}`,
},
};
const res = await req.put(request_url, body, request_config);
}
async function controlStream() {
const input = var_get()['RTMSConsent.Digits'];
const engagementId = var_get()['global_system.Engagement.engagementId'];
const clientId = var_get()['global_custom.**rtms_external**.clientId'];
if (input == 1) {
return await startRTMS(token, engagementId, clientId);
}
}
Conclusion
Manual control of RTMS streams gives Contact Center administrators the flexibility to manage media streams precisely when needed. Whether implementing time-based automation, responding to user interactions, or integrating stream control into call flows, the RTMS API provides powerful customization options for your streaming experience.
Get started and begin building your custom stream management solution today!

