Designing Recording Quality Monitoring Dashboards with Audio Clarity and Silence Detection

Designing Recording Quality Monitoring Dashboards with Audio Clarity and Silence Detection

What This Guide Covers

This guide details the architectural implementation of a Real-Time Quality Monitoring dashboard in Genesys Cloud CX that visualizes audio clarity metrics and silence detection events. The end result is a custom HTML5 widget embedded in the Supervisor dashboard that aggregates WebRTC telemetry and Architect flow data to flag low-quality interactions and excessive dead air before they impact customer satisfaction scores.

Prerequisites, Roles & Licensing

  • Licensing Tier: Genesys Cloud CX 1 (or higher) with Workforce Engagement Management (WEM) add-on for historical recording analysis, and Real-Time Analytics license for live widget data.
  • Permissions:
    • Analytics > Report > Read
    • Analytics > Report > Edit
    • Architect > Flow > Read
    • User Management > User > Read (for agent context)
  • OAuth Scopes: analytics:report:read, architect:flow:read
  • External Dependencies: A Genesys Cloud Architect flow configured to emit silence duration events via Flow Data or Custom Event integration.

The Implementation Deep-Dive

1. Architecting the Silence Detection Logic

Before building the visualization, you must define what constitutes “silence” in your specific telephony environment. Relying solely on the default WebRTC packet loss metrics is insufficient because it does not distinguish between network-induced gaps and conversational pauses. You need to instrument the Architect flow to track silence explicitly.

The Silent Timeout Configuration

In Genesys Cloud, silence detection is often a combination of codec behavior and explicit timeout logic. The most robust method involves using the Listen or Play blocks with silence detection enabled, or leveraging the Flow Data feature to track time spent in specific nodes without voice activity.

We use a Flow Data approach because it provides granular control over when silence starts and stops.

Step 1.1: Define the Silence Threshold
Determine your business rule. For a call center, silence exceeding 10 seconds is typically considered “dead air” requiring agent intervention. For a callback queue, silence exceeding 30 seconds might indicate a hung-up call that the system failed to detect immediately.

Step 1.2: Implement the Timer in Architect
You cannot simply measure silence in real-time within the UI without backend instrumentation. You must push this data to the analytics engine.

  1. Navigate to Architect > Flows.
  2. Open your primary Voice flow.
  3. Locate the Listen block where the agent or customer speaks.
  4. Enable Silence Detection on the block. Set the Silence Timeout to your threshold (e.g., 10s).
  5. On the Silence Timeout branch, add a Set Flow Data block.

Step 1.3: Emit the Metric
Create a Flow Data item named silence_event_count (type: Integer) and increment it. More importantly, create a Flow Data item named last_silence_duration (type: Float) to capture the exact duration if you are using a custom timer logic.

However, for real-time dashboarding, we rely on the Interaction Data stream. Genesys Cloud automatically captures audio_quality metrics for WebRTC interactions. For traditional SIP/PSTN calls, you must ensure your trunk supports RFC 3711 (SRTP) or that your SBC logs are integrated, but for the dashboard, we focus on the WebRTC/Agent Desktop side where telemetry is native.

The Trap: Confusing Network Silence with Conversational Silence
A common misconfiguration is treating all silence as a quality issue. If an agent is transferring a call or looking up customer records, silence is expected. If you flag every 10-second pause, your dashboard becomes noise.

The Solution: Use Flow Data to tag the interaction context. When the agent enters a “Research” or “Transfer” state, set a Flow Data flag is_busy_researching=true. Your dashboard query must filter out silence events where this flag is true. This requires pushing Flow Data to the Interaction Data stream via the Analytics settings in the flow.

2. Constructing the Real-Time Analytics Query

The dashboard requires a data source that updates frequently. We use the Real-Time Analytics API to fetch interaction data. The key is filtering for interactions that have specific audio quality attributes.

Step 2.1: Identify the Data Fields

In the Genesys Cloud Analytics schema, audio quality is exposed through the audio_quality object within the interaction data. Key fields include:

  • audio_quality.jitter_ms: Jitter in milliseconds.
  • audio_quality.packet_loss_percent: Percentage of lost packets.
  • audio_quality.round_trip_time_ms: Latency.

For silence, we look at interaction_data.duration versus interaction_data.talk_time. However, real-time silence is better derived from the Speech Analytics or custom Flow Data if pushed to analytics. Since pushing Flow Data to real-time analytics has a latency of ~10-30 seconds, we often combine real-time WebRTC stats with near-real-time Flow Data.

The Architectural Decision:
We will build a hybrid query.

  1. Real-Time: Fetch active interactions with poor WebRTC audio quality (Jitter > 30ms or Packet Loss > 2%).
  2. Near-Real-Time: Fetch interactions where silence_event_count > 0 from the last 5 minutes.

Step 2.2: The API Payload

We use the POST /api/v2/analytics/report/real-time/query endpoint.

{
  "type": "interaction",
  "interval": "PT1H",
  "where": [
    {
      "path": "type",
      "value": "voice",
      "operator": "eq"
    },
    {
      "path": "status",
      "value": "active",
      "operator": "eq"
    },
    {
      "path": "audio_quality.packet_loss_percent",
      "value": 2.0,
      "operator": "gt"
    }
  ],
  "groupBy": [
    {
      "path": "participant.type",
      "name": "participantType"
    },
    {
      "path": "audio_quality.jitter_ms",
      "name": "jitter"
    }
  ],
  "data": [
    {
      "path": "audio_quality.packet_loss_percent",
      "name": "packetLoss",
      "type": "avg"
    },
    {
      "path": "audio_quality.round_trip_time_ms",
      "name": "latency",
      "type": "avg"
    },
    {
      "path": "silence_event_count",
      "name": "silenceCount",
      "type": "sum"
    }
  ]
}

Note on Field Paths:
The silence_event_count field path depends on your Flow Data configuration. If you named the Flow Data item silence_event_count, the path in Analytics is usually flow_data.silence_event_count. You must verify this in the Analytics > Fields explorer.

The Trap: Aggregation Latency
Real-time analytics are not instant. There is a processing pipeline. If you poll every 1 second, you will receive stale data and hit API rate limits.

The Solution: Poll the endpoint every 10-15 seconds. Use the interval parameter to request data for the last 1 hour, but filter the results client-side to only show interactions that are currently active or completed within the last 5 minutes.

3. Building the Custom HTML5 Widget

We embed this logic into a custom widget using the Genesys Cloud Widgets SDK. This allows us to place the dashboard in the Supervisor view.

Step 3.1: Widget Structure

Create a standard HTML5 widget structure. We will use a simple table to display agents with poor audio quality or high silence counts.

<!DOCTYPE html>
<html>
<head>
    <title>Audio Quality Monitor</title>
    <style>
        .bad-quality { color: red; font-weight: bold; }
        .good-quality { color: green; }
        table { width: 100%; border-collapse: collapse; }
        th, td { padding: 8px; text-align: left; border-bottom: 1px solid #ddd; }
    </style>
    <script src="https://assets.genesiscloud.com/widgets-sdk/1.0.0/widgets-sdk.js"></script>
</head>
<body>
    <div id="dashboard">
        <h3>Real-Time Audio Quality & Silence Monitor</h3>
        <table id="qualityTable">
            <thead>
                <tr>
                    <th>Agent</th>
                    <th>Packet Loss (%)</th>
                    <th>Jitter (ms)</th>
                    <th>Silence Events</th>
                    <th>Status</th>
                </tr>
            </thead>
            <tbody id="tableBody">
                <!-- Data injected here -->
            </tbody>
        </table>
    </div>
    <script src="app.js"></script>
</body>
</html>

Step 3.2: The JavaScript Logic

The app.js file handles the API calls and DOM updates.

let pollingInterval;

function init() {
    // Start polling every 15 seconds
    pollingInterval = setInterval(fetchQualityData, 15000);
    fetchQualityData(); // Initial load
}

async function fetchQualityData() {
    try {
        // Get the current user's auth token via the SDK
        const auth = await window.GenesysCloudAuth.getAuth();
        const accessToken = auth.accessToken;

        const payload = {
            "type": "interaction",
            "interval": "PT1H",
            "where": [
                { "path": "type", "value": "voice", "operator": "eq" },
                { "path": "status", "value": "active", "operator": "eq" },
                // Filter for poor quality OR high silence
                {
                    "operator": "or",
                    "conditions": [
                        { "path": "audio_quality.packet_loss_percent", "value": 2.0, "operator": "gt" },
                        { "path": "flow_data.silence_event_count", "value": 0, "operator": "gt" }
                    ]
                }
            ],
            "groupBy": [
                { "path": "participant.name", "name": "agentName" },
                { "path": "participant.id", "name": "agentId" }
            ],
            "data": [
                { "path": "audio_quality.packet_loss_percent", "name": "packetLoss", "type": "avg" },
                { "path": "audio_quality.jitter_ms", "name": "jitter", "type": "avg" },
                { "path": "flow_data.silence_event_count", "name": "silenceCount", "type": "sum" }
            ]
        };

        const response = await fetch('https://api.us.genesys.cloud/api/v2/analytics/report/real-time/query', {
            method: 'POST',
            headers: {
                'Authorization': `Bearer ${accessToken}`,
                'Content-Type': 'application/json'
            },
            body: JSON.stringify(payload)
        });

        if (!response.ok) {
            throw new Error(`HTTP error! status: ${response.status}`);
        }

        const data = await response.json();
        updateDashboard(data.entities);

    } catch (error) {
        console.error('Error fetching quality data:', error);
    }
}

function updateDashboard(entities) {
    const tbody = document.getElementById('tableBody');
    tbody.innerHTML = ''; // Clear existing data

    if (!entities || entities.length === 0) {
        tbody.innerHTML = '<tr><td colspan="5">No quality issues detected.</td></tr>';
        return;
    }

    entities.forEach(entity => {
        const row = document.createElement('tr');
        const agentName = entity.agentName || 'Unknown';
        const packetLoss = entity.packetLoss ? entity.packetLoss.toFixed(2) : '0.00';
        const jitter = entity.jitter ? entity.jitter.toFixed(2) : '0.00';
        const silenceCount = entity.silenceCount || 0;

        // Determine status color
        let statusClass = 'good-quality';
        if (packetLoss > 2.0 || silenceCount > 2) {
            statusClass = 'bad-quality';
        }

        row.innerHTML = `
            <td>${agentName}</td>
            <td class="${statusClass}">${packetLoss}%</td>
            <td class="${statusClass}">${jitter} ms</td>
            <td class="${statusClass}">${silenceCount}</td>
            <td>${statusClass === 'bad-quality' ? 'Action Required' : 'Normal'}</td>
        `;
        tbody.appendChild(row);
    });
}

// Initialize when DOM is ready
document.addEventListener('DOMContentLoaded', init);

The Trap: CORS and Authentication
If you host this widget on an external server, you must configure CORS in the Genesys Cloud Security settings. However, for internal dashboards, it is safer to host the widget files in the Genesys Cloud Content Management System or use a secure CDN that supports the necessary headers.

The Architectural Reasoning:
We use fetch directly in the browser widget. This is only possible if the widget is running in a context where the Genesys Cloud Auth SDK is available and the API endpoints are accessible. If you are building a standalone external dashboard, you must use a backend proxy to handle OAuth token refresh and avoid exposing client secrets.

4. Integrating Silence Detection with Speech Analytics

While the real-time dashboard handles active calls, you need historical analysis to train agents. Genesys Cloud Speech Analytics can be configured to detect silence, but it is primarily designed for keyword spotting.

To bridge this gap, we use Custom Metrics in WEM.

Step 4.1: Define a WEM Metric

  1. Go to Workforce Engagement Management > Metrics.
  2. Create a new Interaction Metric.
  3. Name it Silence_Duration_Total.
  4. Set the source to Flow Data.
  5. Map it to the total_silence_duration Flow Data item you configured in Step 1.

Step 4.2: Add to Evaluation Form

  1. Go to WEM > Evaluations > Evaluation Forms.
  2. Add the Silence_Duration_Total metric to your form.
  3. Set a threshold: If Silence_Duration_Total > 60 seconds, deduct 10 points from the “Communication” category.

The Trap: Double Counting
Do not count silence that occurs during system prompts (e.g., “Please wait while we connect you”). Ensure your Architect flow resets the silence counter or excludes time spent in Play blocks from the total_silence_duration calculation. You can do this by setting total_silence_duration = 0 at the start of each Play block.

Validation, Edge Cases & Troubleshooting

Edge Case 1: WebRTC vs. PSTN Discrepancy

The Failure Condition: The dashboard shows perfect audio quality for an agent, but the customer complains about choppy audio.
The Root Cause: The dashboard only monitors the WebRTC stream between the Agent Desktop and the Genesys Cloud platform. It does not monitor the PSTN leg between Genesys Cloud and the customer’s carrier.
The Solution: Integrate SIP Trunk Monitoring data. If you use a Genesys Cloud SIP trunk, enable Trunk Monitoring in Telephony > Trunks. This provides metrics on the server-to-server leg. For PSTN legs, you must rely on carrier-provided logs or a third-party SIP monitoring tool (like SolarWinds or PRTG) that can ingest SIP signaling data. The dashboard should display a “PSTN Leg Unknown” warning if trunk monitoring data is not available.

Edge Case 2: High Silence Counts in IVR

The Failure Condition: The dashboard flags high silence counts for interactions that never reached an agent.
The Root Cause: Customers often pause while listening to long IVR menus. The Listen block in the IVR flow may detect this as silence if the silence timeout is too aggressive.
The Solution: Differentiate between IVR silence and Agent silence. In your Analytics query, add a filter for flow.id or flow.name to exclude IVR-only flows. Alternatively, add a Flow Data flag is_ivr=true and filter it out in the dashboard query.

Edge Case 3: Packet Loss Misinterpretation

The Failure Condition: The dashboard flags an agent for high packet loss, but the audio sounds fine.
The Root Cause: Some codecs (like Opus) are resilient to packet loss. A 1% packet loss might be imperceptible to the human ear but triggers the dashboard’s threshold.
The Solution: Adjust the thresholds based on codec type. If you are using Opus, increase the packet loss threshold to 5%. If you are using G.711, keep it at 1-2%. You can determine the codec from the interaction_data.media_config field in the Analytics API.

Official References