Building a Custom Supervisor Assist Panel Using the Genesys Cloud Coaching API and React
What This Guide Covers
You are building a React-based supervisor dashboard that leverages the Genesys Cloud Coaching API to identify active interactions, initiate real-time coaching sessions, and retrieve associated transcripts and metadata. The final application provides a unified interface for supervisors to monitor agents, trigger coaching workflows programmatically, and access interaction artifacts without relying on the native Genesys Cloud UI navigation.
Prerequisites, Roles & Licensing
Licensing Requirements
- Genesys Cloud CX 2 or CX 3: The Coaching API exposes full functionality only on CX 2 and higher tiers. CX 1 restricts coaching metadata and real-time capabilities.
- Workforce Engagement Management (WEM) Add-on: Not strictly required for basic coaching initiation, but necessary if your panel integrates with WEM session recordings or quality management scoring via the Quality API.
Permission Strings
The OAuth token must represent a user with the following granular permissions. Service accounts cannot inherit these permissions via role assignment; they must be explicitly granted if used for background processes, though user-context authentication is mandatory for supervisor-facing UIs.
Coaching > Coaching > ReadCoaching > Coaching > WriteInteractions > Conversation > ReadInteractions > Transcript > ReadUsers > User > ReadRouting > Queue > Read(Optional, for queue-based agent browsing)
OAuth Scopes
When configuring the OAuth2 Application in Genesys Cloud, request the following scopes. Over-scoping triggers security reviews; request only what the panel consumes.
coaching:readcoaching:writeconversation:readtranscript:readuser:readlogin:read(Required for token introspection and user profile retrieval)
External Dependencies
- React 18+ with TypeScript.
- Node.js environment for build tooling.
- Access to Genesys Cloud Developer Center for API key generation.
- Event Streams subscription configured if using real-time push updates.
The Implementation Deep-Dive
1. Authentication Strategy and User Context Enforcement
The foundation of a secure supervisor panel is the authentication context. You must authenticate as the logged-in supervisor, not as a service account. Using a service account for a supervisor UI breaks Role-Based Access Control (RBAC). A service account with coaching:write can initiate coaching on any agent in the organization, regardless of the supervisor’s actual permissions. This creates audit gaps and allows unauthorized coaching, violating compliance frameworks like HIPAA and PCI-DSS.
We use the OAuth2 Authorization Code Flow with PKCE. The React application redirects the supervisor to the Genesys Cloud login endpoint. Upon successful authentication, the identity provider returns an authorization code, which the backend exchanges for an access token and refresh token. The frontend stores the token in memory only; never persist tokens in localStorage due to XSS vulnerability risks.
The Trap: Storing the access token in localStorage or sessionStorage. Modern browsers are susceptible to XSS attacks where malicious scripts injected via third-party widgets or compromised dependencies can read storage. If an attacker exfiltrates the token, they gain full access to the coaching API. The secure pattern is to store the token in an HttpOnly cookie set by a backend-for-frontend (BFF) service, or in memory with a silent refresh mechanism that requests a new token before expiry.
Architectural Reasoning: We enforce user context because the Coaching API respects the userId in the token for permission checks. When you call POST /api/v2/coaching/coachings, Genesys validates that the token holder has the right to coach the target userId. If the token represents a service account, this validation is bypassed or behaves unpredictably depending on the service account’s role assignments. User context ensures the panel only surfaces agents the supervisor is authorized to monitor.
Code: Token Refresh Hook
import { useState, useEffect, useCallback } from 'react';
import axios from 'axios';
interface TokenState {
accessToken: string | null;
refreshToken: string | null;
isLoading: boolean;
}
export const useAuthTokens = () => {
const [tokens, setTokens] = useState<TokenState>({
accessToken: null,
refreshToken: null,
isLoading: true,
});
const refreshTokens = useCallback(async () => {
try {
const response = await axios.post('/api/auth/refresh', {
refreshToken: tokens.refreshToken,
});
setTokens({
accessToken: response.data.accessToken,
refreshToken: response.data.refreshToken,
isLoading: false,
});
} catch (error) {
// Redirect to login on refresh failure
window.location.href = '/login';
}
}, [tokens.refreshToken]);
useEffect(() => {
// Initial token fetch or validation
refreshTokens();
}, [refreshTokens]);
return { ...tokens, refreshTokens };
};
2. Retrieving Active Interactions and Agent State
A supervisor assist panel must display active interactions to allow coaching initiation. The naive approach is to poll GET /api/v2/conversations with filters. This approach fails under load. The Conversation API returns comprehensive interaction data including participants, media, and metadata. Polling this endpoint for a dashboard with 50+ agents generates excessive API traffic, triggers rate limiting, and introduces latency in the UI.
We use the Routing API to fetch active assignments, then drill down into specific conversations only when the supervisor selects an agent. The endpoint GET /api/v2/routing/users/{userId}/active-queues returns the queues where the agent is currently active. From there, we use GET /api/v2/routing/users/{userId}/active-interactions to retrieve lightweight interaction summaries.
The Trap: Polling GET /api/v2/conversations with type=voice or type=chat filters. This endpoint returns the full conversation object. When an agent has 10 active interactions, each payload can exceed 20KB. For a supervisor monitoring 20 agents, a 5-second poll interval results in 40MB of data transfer per minute. This saturates the network and hits the API rate limit of 100 requests per second per tenant. The Routing API endpoints are optimized for this use case and return only the identifiers and status needed for the dashboard.
Architectural Reasoning: We separate the “browse” view from the “detail” view. The browse view uses Routing APIs to list agents and their active interaction counts. The detail view, triggered by user interaction, fetches the full conversation and transcript data. This lazy-loading pattern reduces initial payload size and aligns API usage with user intent.
Code: Fetch Active Interactions
export const fetchActiveInteractions = async (
accessToken: string,
agentUserId: string
) => {
const response = await axios.get(
`/api/v2/routing/users/${agentUserId}/active-interactions`,
{
headers: {
Authorization: `Bearer ${accessToken}`,
'Content-Type': 'application/json',
},
}
);
return response.data.entities;
};
3. Initiating a Coaching Session
The core function of the panel is to start a coaching session. The endpoint POST /api/v2/coaching/coachings creates a coaching record. The payload must include the interactionId, userId (the agent), and coachingType. The coachingType determines how the session appears in native Genesys tools. Use REAL_TIME for live coaching and POST_CALL for post-interaction review.
The Trap: Setting the status field to IN_PROGRESS or COMPLETED during creation. The Coaching API manages state transitions. When you create a coaching, the system sets the status to STARTED. If you force the status to COMPLETED, the API rejects the request with a 400 Bad Request because the state machine does not allow jumping directly to completion. Additionally, omitting the interactionId creates a “general” coaching session that is not linked to a specific conversation. This breaks the association between the coaching record and the transcript, making it impossible to retrieve the interaction context later.
Architectural Reasoning: We include custom metadata in the metadata field to track panel-specific actions. Genesys allows arbitrary key-value pairs in coaching metadata. We store the supervisor’s panel session ID and the timestamp of the initiation. This metadata persists with the coaching record and can be queried later for analytics or audit purposes. It also allows the panel to rehydrate state if the browser refreshes, by fetching coachings with the metadata filter.
Code: Initiate Coaching Payload
POST /api/v2/coaching/coachings
{
"interactionId": "conv-a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"userId": "user-agent-uuid-12345",
"coachingType": "REAL_TIME",
"metadata": {
"source": "custom-supervisor-panel",
"panelSessionId": "sess-xyz-987",
"initiatedAt": "2023-10-27T14:30:00Z"
}
}
React Implementation:
export const initiateCoaching = async (
accessToken: string,
interactionId: string,
agentUserId: string,
sessionId: string
) => {
const payload = {
interactionId,
userId: agentUserId,
coachingType: "REAL_TIME" as const,
metadata: {
source: "custom-supervisor-panel",
panelSessionId: sessionId,
initiatedAt: new Date().toISOString(),
},
};
try {
const response = await axios.post(
'/api/v2/coaching/coachings',
payload,
{
headers: {
Authorization: `Bearer ${accessToken}`,
'Content-Type': 'application/json',
},
}
);
return response.data;
} catch (error: any) {
if (error.response?.status === 409) {
throw new Error(
'A coaching session already exists for this interaction. Use the existing session.'
);
}
throw error;
}
};
4. Real-Time State Updates via Event Streams
Polling for coaching status changes is inefficient and introduces latency. A supervisor needs to see when a coaching session is updated, paused, or completed in real time. We subscribe to the Event Streams WebSocket to receive coaching.* events.
The Trap: Subscribing to coaching.* without filtering. The event stream delivers coaching events for the entire tenant. If you subscribe globally, the WebSocket receives updates for every coaching session initiated by every supervisor. This floods the React component tree with irrelevant updates, causing performance degradation and UI freezes. You must filter events by the userId of the supervisor or by the interactionId of the active view.
Architectural Reasoning: We use a normalized state structure in React to handle event stream updates. Instead of updating nested objects directly, we maintain a flat map of coaching records keyed by coachingId. When an event arrives, we update the specific record in the map. This O(1) update pattern ensures the UI remains responsive even under high event throughput. We also implement a reconciliation step that discards events older than the current UI state to prevent race conditions.
Code: Event Stream Subscription
import { EventStreamsClient } from '@genesyscloud/event-streams-client';
export const subscribeToCoachingEvents = (
accessToken: string,
onCoachingUpdate: (event: any) => void
) => {
const client = new EventStreamsClient({
token: accessToken,
});
const subscription = {
events: ['coaching.*'],
filters: [
{
name: 'coaching.userId',
operator: 'eq',
value: 'current-supervisor-uuid', // Filter by supervisor context
},
],
};
client.subscribe(subscription, (event) => {
onCoachingUpdate(event);
});
return () => {
client.unsubscribe(subscription);
};
};
5. Transcript Retrieval and PII Handling
The panel must display transcripts for the active interaction. The endpoint GET /api/v2/conversations/{conversationId}/transcripts returns transcript segments. For voice interactions, transcripts are generated asynchronously. The API may return a 404 or an empty list immediately after the interaction starts.
The Trap: Storing transcript data in the React component state indefinitely. Transcripts contain Personally Identifiable Information (PII) such as names, phone numbers, and payment details. If the panel caches transcripts in memory or local storage, it becomes a data reservoir that must be protected under GDPR, CCPA, and HIPAA. If the supervisor navigates away from the interaction, the transcript data should be purged from memory. Additionally, failing to handle transcript availability latency causes the UI to display “No Transcript” errors, confusing the supervisor.
Architectural Reasoning: We implement a retry mechanism with exponential backoff for transcript fetching. If the transcript is not available, the UI displays a “Transcript Pending” state and retries every 3 seconds, up to 10 attempts. After the limit, the UI shows a “Transcript Unavailable” error with a manual refresh button. We also implement a data scrubber that redacts PII patterns in the transcript before rendering, using a regex-based sanitizer. This reduces the risk of PII exposure in screenshots or logs.
Code: Transcript Fetch with Retry
export const fetchTranscriptWithRetry = async (
accessToken: string,
conversationId: string,
maxRetries = 10,
delayMs = 3000
): Promise<any[]> => {
let attempts = 0;
while (attempts < maxRetries) {
try {
const response = await axios.get(
`/api/v2/conversations/${conversationId}/transcripts`,
{
headers: {
Authorization: `Bearer ${accessToken}`,
},
}
);
// Filter and redact PII before returning
const sanitizedTranscript = response.data.entities.map(segment => ({
...segment,
text: redactPII(segment.text),
}));
return sanitizedTranscript;
} catch (error: any) {
if (error.response?.status === 404) {
attempts++;
if (attempts === maxRetries) {
throw new Error('Transcript generation timed out.');
}
await new Promise(resolve => setTimeout(resolve, delayMs * attempts));
} else {
throw error;
}
}
}
return [];
};
Validation, Edge Cases & Troubleshooting
Edge Case 1: The “Zombie” Coaching Session
The Failure Condition: The supervisor clicks “Start Coaching,” the API returns 201 Created, but the coaching session does not appear in the native Genesys Cloud UI, and the agent does not receive the coaching notification.
The Root Cause: The interaction ended between the time the panel fetched the interaction status and the time the coaching API processed the creation request. The Coaching API creates the record, but the interaction lifecycle manager marks the session as invalid because the conversation is closed. The coaching record exists in the database but is detached from the interaction.
The Solution: Implement a pre-flight check. Before calling POST /api/v2/coaching/coachings, fetch the interaction status via GET /api/v2/conversations/{conversationId}. Verify the status is ACTIVE. If the status is COMPLETED or WRAPPED_UP, prevent the coaching initiation and display a warning. Additionally, handle the 409 Conflict response from the coaching API, which indicates a session already exists or the interaction is invalid.
Edge Case 2: Token Expiry During Long Coaching Sessions
The Failure Condition: The supervisor leaves the panel open for 4 hours. The OAuth token expires after 1 hour. Subsequent API calls to update coaching metadata or fetch transcripts fail with 401 Unauthorized. The UI becomes unresponsive.
The Root Cause: The access token has a limited lifetime. The React application does not implement a silent refresh mechanism. The user is not notified of the token expiry, and the panel silently fails.
The Solution: Implement a token refresh hook that monitors the token expiry time. When the token is within 5 minutes of expiry, trigger a refresh using the refresh token. If the refresh fails, redirect the supervisor to the login page. Use an interceptor in the Axios instance to catch 401 responses and retry the request after a successful token refresh. This ensures seamless operation during long sessions.
Edge Case 3: Cross-Region Latency in Transcript Availability
The Failure Condition: The supervisor is in the US-EAST-1 region, and the agent is in AP-SOUTH-1. Voice transcripts take significantly longer to appear in the API response compared to same-region interactions. The panel shows “Transcript Pending” for over 2 minutes.
The Root Cause: Genesys Cloud processes voice transcripts in the region where the interaction is anchored. Cross-region replication introduces latency in the transcript API response. The Event Stream for transcripts may also be delayed.
The Solution: Adjust the retry logic based on the interaction region. Fetch the interaction metadata to determine the region. If the region differs from the supervisor’s region, increase the maxRetries and delayMs in the transcript fetch function. Display a contextual message to the supervisor: “Transcript may take longer due to cross-region processing.” This manages expectations and prevents unnecessary error reporting.
Edge Case 4: Metadata Size Limit Exceeded
The Failure Condition: The panel attempts to store extensive metadata in the coaching record, such as full JSON objects of agent performance scores. The API returns 400 Bad Request with a validation error.
The Root Cause: The Coaching API enforces a size limit on the metadata field. Exceeding this limit causes the request to fail.
The Solution: Limit the metadata payload to essential identifiers and timestamps. Store detailed performance data in an external database or object storage, and reference the external record ID in the metadata. This keeps the coaching record lightweight and compliant with API limits.