Implementing Custom Wallboard Animations for Real-Time Celebration of Agent Milestones

Implementing Custom Wallboard Animations for Real-Time Celebration of Agent Milestones

What This Guide Covers

This guide details the architecture and implementation of a dynamic Genesys Cloud CX wallboard that triggers visual animations when an agent achieves specific real-time milestones, such as handling 100 consecutive calls without hold time. The end result is a production-ready HTML/CSS/JavaScript widget embedded within the Admin Wallboard editor that consumes Real-Time Analytics APIs to update state and render GPU-accelerated animations without performance degradation.

Prerequisites, Roles & Licensing

To execute this implementation, the environment must meet specific licensing and permission requirements. Standard wallboard capabilities do not support complex JavaScript logic or API polling out of the box; you require the Genesys Cloud CX platform with the Admin Console access level.

Required Licensing Tiers:

  • Genesys Cloud CX (PureCloud) Enterprise Edition: Basic wallboards are available on lower tiers, but custom scripting support and high-frequency Real-Time API access typically require Enterprise or Premier licensing levels.
  • WEM Add-on (Workforce Engagement Management): Required if you intend to track historical milestones alongside real-time data for comparative animation triggers.

Granular Permission Strings:
The user account configuring the wallboard requires the following permissions within the Admin Console:

  • Wallboards > Edit: Allows modification of the HTML/CSS/JS editor.
  • Analytics > Reports > Run: Required to execute the Real-Time Reporting API queries.
  • Users > View: Required to map agent IDs to display names during runtime.

OAuth Scopes:
The JavaScript embedded in the wallboard utilizes the Genesys Cloud OAuth 2.0 flow. The script must request the following scopes:

  • genesys.cloud.analytics.read
  • genesys.cloud.users.read

External Dependencies:

  • A dedicated browser environment (Chrome, Edge, or Safari) with JavaScript ES6+ support enabled.
  • Network access from the wallboard viewer client to the Genesys Cloud API endpoints (https://api.mypurecloud.com).

The Implementation Deep-Dive

1. Architectural Design and Data Flow Strategy

The core challenge in building animated wallboards is balancing data freshness with API rate limits. A naive approach involves polling the Real-Time Metrics API every second. This creates significant latency for state changes and risks throttling errors during peak load.

Instead, this implementation utilizes a Hybrid Polling Strategy. The wallboard script polls the GET /api/v2/analytics/reporting/queries endpoint at a configurable interval (default 30 seconds) to fetch aggregate agent data. However, to ensure immediate visual feedback for milestone triggers (e.g., an agent completing their 50th call), we utilize a lightweight CSS State Machine that listens for state changes in the fetched payload.

The Trap:
A common misconfiguration is embedding the API polling logic inside a setInterval loop with no debouncing or error handling. When the wallboard renders on multiple monitors simultaneously, each instance initiates its own independent API call. If five monitors display the same wallboard, you are generating five concurrent API requests every 30 seconds. This rapidly consumes your organization’s API quota limits and can trigger 429 Too Many Requests errors from the Genesys Cloud API, causing the wallboard to fail silently or display stale data for extended periods.

Architectural Reasoning:
To mitigate this, we implement a Singleton Polling Pattern. The script checks if an active polling interval exists before starting a new one. Furthermore, we cache the last known state to prevent redundant DOM reflows. If the API returns the same metrics as the previous poll, the script skips the animation logic entirely, reducing CPU usage on the client device displaying the wallboard.

2. API Integration and Payload Handling

The wallboard must retrieve real-time agent data to compare against milestone thresholds. We target the Real-Time Reporting Query endpoint. This endpoint allows us to filter by specific metrics (e.g., handleTime, waitTime) and group by agent ID.

API Endpoint:
GET /api/v2/analytics/reporting/queries

Request Headers:

Authorization: Bearer <access_token>
Content-Type: application/json

Realistic JSON Body:
The following payload requests the current status of a specific agent, focusing on call counts and handling time. This data serves as the trigger source for the animation logic.

{
  "interval": "PT5M",
  "granularity": "PT1M",
  "metrics": [
    {
      "name": "callCount",
      "aggregationType": "SUM"
    },
    {
      "name": "averageHandleTime",
      "aggregationType": "AVG"
    }
  ],
  "filterExpression": {
    "type": "AND",
    "filters": [
      {
        "metric": "agentId",
        "operator": "EQ",
        "value": "<AGENT_ID_FROM_CONFIG>"
      }
    ]
  },
  "orderBy": "DESC"
}

Implementation Code:
The following JavaScript snippet demonstrates how to fetch this data securely within the wallboard sandbox. We utilize fetch with a promise chain to handle asynchronous state updates.

const GENESYS_API_BASE = 'https://api.mypurecloud.com';
const MILESTONE_THRESHOLD = 100; // Example: Celebrate at 100 calls

async function fetchAgentMetrics(agentId) {
  const token = await getAuthToken(); // Helper to retrieve OAuth token
    
  const payload = {
    interval: 'PT5M',
    granularity: 'PT1M',
    metrics: [
      { name: 'callCount', aggregationType: 'SUM' },
      { name: 'averageHandleTime', aggregationType: 'AVG' }
    ],
    filterExpression: {
      type: 'AND',
      filters: [{ metric: 'agentId', operator: 'EQ', value: agentId }]
    }
  };

  const response = await fetch(`${GENESYS_API_BASE}/api/v2/analytics/reporting/queries`, {
    method: 'POST',
    headers: {
      'Authorization': `Bearer ${token}`,
      'Content-Type': 'application/json'
    },
    body: JSON.stringify(payload)
  });

  if (!response.ok) {
    console.error('API Throttling or Authentication Failure');
    throw new Error('Failed to fetch analytics');
  }

  return await response.json();
}

The Trap:
Developers often hardcode the agentId in the script for simplicity. However, wallboards are frequently reused across multiple agents during a shift change or team rotation. Hardcoding an ID causes the animation to fire only for that specific individual, rendering the dashboard useless for others. Additionally, if the agent logs out before the poll completes, the API may return null data, causing the script to crash if not handled.

Architectural Reasoning:
To resolve this, the wallboard configuration should allow the agentId to be injected as a dynamic variable or passed via the URL query parameters when launching the wallboard view. If the ID is unknown (e.g., “All Agents” view), the script must iterate over an array of active agents and aggregate the call counts. For this specific milestone animation, we assume a Single Agent Focus Mode where the agentId is passed as a URL parameter like ?agentId=123456.

3. Animation Logic and DOM Manipulation

Once the API returns data indicating a milestone has been crossed (e.g., callCount reaches 100), the script must trigger a visual celebration. This requires decoupling the data logic from the rendering logic to ensure performance. We use CSS Keyframes for the animation itself, as they are hardware-accelerated by the GPU, unlike JavaScript-driven frame loops which can cause jank on lower-end devices.

CSS Implementation:
The following CSS defines a confetti explosion effect. This is injected dynamically into the wallboard’s style block to ensure it does not interfere with existing wallboard styles.

@keyframes confettiExplosion {
  0% { transform: translate(0, 0) scale(0); opacity: 1; }
  50% { transform: translate(var(--tx), var(--ty)) scale(1.5); opacity: 0.8; }
  100% { transform: translate(calc(var(--tx) * 2), calc(var(--ty) * 2)) scale(0); opacity: 0; }
}

.milestone-celebration {
  position: absolute;
  top: 50%;
  left: 50%;
  width: 100px;
  height: 100px;
  pointer-events: none;
  z-index: 9999;
}

.confetti-piece {
  position: absolute;
  background-color: #FFD700;
  width: 8px;
  height: 8px;
  animation: confettiExplosion 1.5s ease-out forwards;
}

The Trap:
A frequent error is triggering the animation on every API poll interval when the data has not changed. If the polling interval is 30 seconds and the agent just hit 100 calls, the script will attempt to render the confetti effect again on the next poll. This creates a visual glitch where the celebration loop restarts mid-explosion, appearing as flickering or stuttering graphics.

Architectural Reasoning:
We must implement a State Change Detector. Before triggering the animation function, we compare the current callCount against the stored previousCallCount. The animation only fires if current > threshold AND threshold > previousThreshold. This ensures the celebration event is discrete and singular.

let lastTriggeredMilestone = 0;

function triggerCelebration() {
  const container = document.getElementById('milestone-container');
  
  // Create confetti elements
  for (let i = 0; i < 50; i++) {
    const piece = document.createElement('div');
    piece.className = 'confetti-piece';
    
    // Randomize trajectory variables
    const tx = (Math.random() - 0.5) * 500 + 'px';
    const ty = (Math.random() - 0.5) * 500 + 'px';
    
    piece.style.setProperty('--tx', tx);
    piece.style.setProperty('--ty', ty);
    piece.style.left = '50%';
    piece.style.top = '50%';
    
    container.appendChild(piece);
  }

  // Cleanup after animation completes to prevent DOM bloat
  setTimeout(() => {
    container.innerHTML = '';
  }, 1500);
}

The Trap:
Memory leaks occur when elements are appended to the DOM but never removed. If the wallboard remains open for a full shift (8 hours) and the agent hits multiple milestones, the script might append thousands of div elements without clearing them if the timeout logic fails or the element is hidden rather than removed from the DOM tree.

Architectural Reasoning:
The cleanup function uses setTimeout to remove the container contents after the CSS animation duration (1.5 seconds). This guarantees that the DOM remains clean and does not grow indefinitely. Additionally, we use pointer-events: none on the container to ensure the confetti does not block user interaction if the wallboard is interactive for other reasons.

4. Authentication and Security Context

Genesys Cloud Wallboards execute in a restricted JavaScript sandbox. They cannot directly access local storage or make arbitrary network requests without proper authorization tokens. The script must acquire an OAuth token dynamically before making API calls.

Token Acquisition Logic:
The wallboard environment provides a window.genesys global object that contains authentication utilities. Developers should not implement their own OAuth flow from scratch within the wallboard script. Instead, rely on the platform-provided token management to ensure session validity and automatic refresh.

async function getAuthToken() {
  // Check if Genesys SDK is available in the sandbox
  if (window.genesys && window.genesys.auth) {
    return window.genesys.auth.getAccessToken();
  }
  
  // Fallback for non-embedded contexts or edge cases
  throw new Error('Authentication context not found');
}

The Trap:
Developers sometimes attempt to store OAuth tokens in localStorage within the wallboard script. This is a security vulnerability because wallboards can be viewed on shared kiosks or public screens. If a token persists, it could potentially be exploited by malicious scripts injected into the page context later.

Architectural Reasoning:
We rely strictly on the temporary access token provided by the window.genesys.auth API. This token refreshes automatically in the background and expires according to Genesys Cloud security policies. By avoiding local storage for credentials, we ensure that every time the wallboard loads or the page is refreshed, a fresh authentication handshake occurs with zero persistent risk.

Validation, Edge Cases & Troubleshooting

Edge Case 1: API Latency During Peak Load

The Failure Condition:
During peak call volume, the Real-Time Reporting API may experience increased latency (500ms to 2s). The wallboard appears frozen or displays outdated metrics because the polling interval has not yet completed.

The Root Cause:
The setInterval loop runs on a fixed schedule regardless of network conditions. If the API request hangs, the script blocks further execution until completion or timeout.

The Solution:
Implement an Adaptive Polling Interval. Use a try-catch block around the fetch request. If a 503 Service Unavailable or high latency is detected, increase the polling interval dynamically (e.g., from 30 seconds to 60 seconds) for the next cycle. This reduces server load and prevents cascading failures during network congestion.

let pollInterval = 30000; // Default 30s

async function startPolling() {
  try {
    const data = await fetchAgentMetrics(agentId);
    updateDashboard(data);
    lastPollTime = Date.now();
  } catch (error) {
    console.warn('API Error, extending interval');
    pollInterval = 60000; // Extend to 60s on error
    setTimeout(startPolling, pollInterval);
  }
}

Edge Case 2: Agent ID Mismatch in Aggregated Views

The Failure Condition:
The wallboard is set to view “All Agents” but the animation logic only triggers for one specific agent ID due to a hardcoded filter. The celebration fails to appear when any agent hits the milestone.

The Root Cause:
The API query filters incorrectly or the script assumes a single-agent context when the wallboard scope is team-wide.

The Solution:
Refactor the data processing logic to handle arrays of agents. Iterate through the returned metrics array. If any agent meets the condition, trigger the celebration globally. However, for specific milestone tracking (e.g., “John hits 100 calls”), ensure the UI displays a badge indicating which agent triggered the animation to avoid confusion among the team viewing the board.

Edge Case 3: Browser Performance Degradation

The Failure Condition:
On older monitors or browsers with hardware acceleration disabled, the CSS confetti animation causes frame drops and screen tearing.

The Root Cause:
CSS Keyframes are generally efficient, but if the DOM manipulation happens too rapidly or without GPU layer separation, the browser compositor may struggle.

The Solution:
Force hardware acceleration on the animation container by adding transform: translate3d(0, 0, 0) to the .milestone-celebration CSS class. This promotes the element to its own composition layer in the browser, ensuring smooth rendering regardless of system load. Additionally, limit the number of confetti pieces generated (e.g., max 50) to reduce the rendering cost per frame.

Official References