Skip to main content

Documentation Index

Fetch the complete documentation index at: https://developers.telnyx.com/llms.txt

Use this file to discover all available pages before exploring further.

Call Report Stats

The Telnyx WebRTC JS SDK can automatically collect WebRTC statistics during and after calls. Use call reports to monitor quality, diagnose issues, and build real-time quality indicators.

Enabling Call Reports

const client = new TelnyxRTC({
 login_token: jwt,
 enableCallReports: true, // Required (default: true)
 callReportInterval: 5000, // Stats every 5 seconds (default)
});
OptionTypeDefaultDescription
enableCallReportsbooleantrueEnable call report collection
callReportIntervalnumber5000Interval in ms between periodic stats

Real-Time Stats (telnyx.stats.frame)

Fires periodically during an active call (every callReportInterval ms):
client.on('telnyx.stats.frame', (stats) => {
 console.log('RTT:', stats.rtt);
 console.log('Jitter:', stats.jitter);
 console.log('Packet loss:', stats.packetLoss);
});

StatsFrame Properties

PropertyTypeDescription
rttnumberRound-trip time in milliseconds
jitternumberJitter in milliseconds
packetLossnumberPacket loss percentage
bytesSentnumberTotal bytes sent
bytesReceivednumberTotal bytes received
packetsSentnumberTotal RTP packets sent
packetsReceivednumberTotal RTP packets received
packetsLostnumberTotal RTP packets lost
audioLevelnumberCurrent audio level (0.0 - 1.0)
timestampnumberUnix timestamp of the measurement

Quality Thresholds

MetricGoodFairPoor
RTT< 150ms150-300ms> 300ms
Jitter< 20ms20-50ms> 50ms
Packet Loss< 1%1-3%> 3%

Building a quality indicator

client.on('telnyx.stats.frame', (stats) => {
 let quality = 'excellent';

 if (stats.rtt > 300 || stats.packetLoss > 3) {
 quality = 'poor';
 } else if (stats.rtt > 150 || stats.packetLoss > 1) {
 quality = 'fair';
 }

 updateQualityIndicator(quality);
});

End-of-Call Report (telnyx.stats.report)

Fires when a call ends with a summary of the entire call:
client.on('telnyx.stats.report', (report) => {
 console.log('Call ended:', report.callId);
 console.log('Duration:', report.duration, 'seconds');
 console.log('Average RTT:', report.avgRtt);
});

Call Report Stats API

For SDK 2.25.20+, call reports are also available via HTTP API:
# Get full call report with ICE data
curl "http://voice-sdk-call-report-stats.query.prod.telnyx.io:4000/api/v1/calls/{user_id}/{call_id}"

# Get ICE candidate data
curl "http://voice-sdk-call-report-stats.query.prod.telnyx.io:4000/api/v1/calls/{user_id}/{call_id}/ice"

API Response Structure

{
 "data": {
 "call": {
 "call_id": "98041520-...",
 "duration": 45,
 "sdk_version": "2.26.3",
 "telnyx_session_id": "...",
 "telnyx_leg_id": "..."
 },
 "segments": [
 {
 "timestamp": 1712000000,
 "bytesSent": 12345,
 "bytesReceived": 23456,
 "rtt": 45,
 "jitter": 3,
 "audioLevel": 0.5
 }
 ],
 "ice_data": {
 "transport": {
 "dtls_state": "connected",
 "ice_state": "connected",
 "srtp_cipher": "AES_CM_128_HMAC_SHA1_80"
 },
 "selected_pair": {
 "local_candidate": { "type": "relay", "ip": "64.16.248.1", "port": 50000 },
 "remote_candidate": { "type": "host", "ip": "10.239.207.80", "port": 50001 },
 "nominated": true,
 "state": "succeeded"
 },
 "candidates": [
 { "type": "host", "ip": "192.168.1.5", "port": 50000, "timestamp": 1712000000 },
 { "type": "srflx", "ip": "203.0.113.5", "port": 50000, "timestamp": 1712000001 },
 { "type": "relay", "ip": "64.16.248.1", "port": 50000, "timestamp": 1712000002 }
 ]
 },
 "logs": ["SDK console log entries if captured"]
 }
}

Key Fields for Diagnostics

FieldPathWhat It Tells You
DTLS stateice_data.transport.dtls_state"connected" = media encrypted , "connecting" = DTLS stuck
ICE stateice_data.transport.ice_state"connected" = ICE worked
SRTP cipherice_data.transport.srtp_cipherNull = no encryption (DTLS failed)
Selected pairice_data.selected_pairWhich candidate pair is actually in use
Candidate typesice_data.candidates[].typehost = direct, srflx = STUN, relay = TURN

Diagnosing Issues from Call Reports

DTLS stuck (“connecting”)

ice_data.transport.dtls_state: "connecting"
ice_data.transport.ice_state: "connected"
ice_data.transport.srtp_cipher: null
Cause: ICE succeeded but DTLS handshake failed. Usually a network issue where DTLS packets from one side aren’t reaching the other (asymmetric routing, multiple NICs, firewall). Action: Check if client has multiple network interfaces. See Best Practices → Network.

All relay candidates

ice_data.candidates: [
 { type: "relay", ... },
 { type: "relay", ... }
]
Cause: Client can’t generate host or srflx candidates. Likely behind strict NAT or VPN. Action: Check firewall settings. If expected (e.g., for privacy), set forceRelayCandidate: true.

No candidates at all

ice_data.candidates: []
Cause: STUN/TURN servers unreachable, or browser denied media permissions before ICE gathering started. Action: Check network connectivity to stun.telnyx.com and turn.telnyx.com. See Network Requirements.

See Also