·19 min·1 views

How to Set Up Call Recording Analytics in Asterisk: The Complete Guide

Transform raw call recordings into actionable insights with storage architecture, CDR linkage, SQL analytics, AI speech analytics with Whisper, and compliance best practices.

A
Astervis
Engineering & product team

Recording calls in Asterisk is easy. Getting actionable insights from those recordings? That's where most teams struggle.

If you've already configured MixMonitor or Monitor in your dialplan, you're sitting on a goldmine of data. The problem is that most Asterisk deployments treat recordings as a compliance checkbox — files sitting in /var/spool/asterisk/monitor/ that nobody touches until there's a dispute.

This guide shows you how to transform raw call recordings into a structured analytics system. You'll learn how to organize recordings for fast retrieval, link them to CDR metadata, build dashboards that surface quality issues automatically, and even add AI-powered speech analytics using open-source tools.

Why Call Recording Analytics Matters

Call recording without analytics is like having security cameras but never reviewing the footage.

Here's what recording analytics enables:

Use CaseWithout AnalyticsWith Analytics
Quality assuranceRandom spot-checks (2-5% coverage)Systematic scoring across 100% of calls
Agent coachingSubjective feedback based on memoryData-driven coaching with specific examples
Compliance verificationManual review when complaints ariseAutomated flagging of compliance gaps
Customer insightsAnecdotal observationsTrend analysis across thousands of interactions
Dispute resolutionManual search through file systemInstant retrieval linked to call metadata

Organizations using call recording analytics report 23% improvement in first-call resolution and 18% reduction in average handle time within the first quarter of implementation (ICMI Contact Center Research, 2024).

Step 1: Recording Setup Foundation

Before building analytics, you need a solid recording foundation. Asterisk provides two main applications:

MixMonitor vs Monitor

FeatureMixMonitorMonitor
Audio mixingRecords both sides in one fileSeparate files per direction (or mixed)
Performance impactLightweight (recommended)Heavier, may cause audio issues
In-call controlStart/stop/pause dynamicallyLimited control
Recording formatWAV, WAV49, GSM, SLN, SLINWAV, WAV49, GSM
Channel requirementWorks on answered channelsWorks on answered channels
Recommended forProduction call centersLegacy setups

Use MixMonitor. It's the modern, production-ready choice.

Basic MixMonitor Dialplan

; extensions.conf — Recording with structured filenames [macro-record-call] exten => s,1,NoOp(Starting call recording) same => n,Set(RECORD_DIR=/var/spool/asterisk/monitor/${STRFTIME(${EPOCH},,%Y/%m/%d)}) same => n,System(mkdir -p ${RECORD_DIR}) same => n,Set(RECORD_FILE=${RECORD_DIR}/${STRFTIME(${EPOCH},,%Y%m%d-%H%M%S)}-${UNIQUEID}-${CALLERID(num)}-${EXTEN}) same => n,Set(CDR(recordingfile)=${RECORD_FILE}.wav) same => n,MixMonitor(${RECORD_FILE}.wav,b) same => n,MacroExit()

Key points in this dialplan:

  • Date-based directories (YYYY/MM/DD) prevent filesystem slowdown from too many files in one folder
  • Structured filenames include timestamp, unique ID, caller ID, and extension — critical for analytics
  • CDR linkage via CDR(recordingfile) connects the recording to call detail records

FreePBX Recording Setup

If you're using FreePBX, call recording is configured per-extension, ring group, or queue:

  1. Navigate to Admin → Extensions → [Extension] → Recording
  2. Set recording policy: Force, Don't Care, Yes, or No
  3. Choose Inbound External, Outbound External, Inbound Internal, Outbound Internal

FreePBX stores recordings in /var/spool/asterisk/monitor/ with the format:

{year}/{month}/{day}/{type}-{date}-{time}-{source}-{destination}-{uniqueid}.wav

Step 2: Storage Architecture for Analytics

A production call center recording system needs careful storage planning. At 64 kbps (G.711 WAV), one hour of recorded calls consumes approximately 28.8 MB. A 50-agent call center averaging 6 hours of talk time per agent per day generates 8.6 GB daily or 260 GB monthly.

Storage Sizing Calculator

AgentsAvg Talk Hours/DayDaily StorageMonthly StorageAnnual Storage
1051.4 GB43 GB516 GB
2553.6 GB108 GB1.3 TB
5068.6 GB260 GB3.1 TB
100617.3 GB518 GB6.2 TB
200634.6 GB1 TB12.4 TB
/var/spool/asterisk/ └── monitor/ ├── 2026/ │ ├── 01/ │ │ ├── 15/ │ │ │ ├── 20260115-093042-1705312242.1-2125551234-100.wav │ │ │ └── ... │ │ └── 16/ │ └── 02/ ├── compressed/ # Archived recordings (MP3/Opus) │ ├── 2025/ │ └── ... └── transcripts/ # AI-generated transcripts ├── 2026/ └── ...

Compression Strategy

WAV files are unnecessarily large for archival. Implement automated compression:

#!/bin/bash # compress-recordings.sh — Run daily via cron # Compresses WAV recordings older than 7 days to Opus format # Opus achieves 10:1 compression vs WAV with excellent quality MONITOR_DIR="/var/spool/asterisk/monitor" COMPRESS_DIR="$MONITOR_DIR/compressed" DAYS_OLD=7 find "$MONITOR_DIR" -name "*.wav" -mtime +$DAYS_OLD \ -not -path "*/compressed/*" | while read wavfile; do # Preserve directory structure relative_path="${wavfile#$MONITOR_DIR/}" opus_path="$COMPRESS_DIR/${relative_path%.wav}.opus" mkdir -p "$(dirname "$opus_path")" # Convert to Opus (high quality, ~1/10 the size) ffmpeg -i "$wavfile" -c:a libopus -b:a 24k \ -application voip "$opus_path" 2>/dev/null if [ $? -eq 0 ] && [ -f "$opus_path" ]; then rm "$wavfile" echo "Compressed: $relative_path" fi done

Add to crontab:

# Run compression daily at 2 AM 0 2 * * * /usr/local/bin/compress-recordings.sh >> /var/log/recording-compression.log 2>&1

Raw recordings become powerful when linked to CDR (Call Detail Record) data. This connection enables queries like "show me all recordings where hold time exceeded 60 seconds" or "find calls from this customer number in the last 30 days."

Database Schema for Recording Analytics

-- Create a recordings metadata table that extends CDR CREATE TABLE recording_metadata ( id SERIAL PRIMARY KEY, uniqueid VARCHAR(64) NOT NULL, linkedid VARCHAR(64), recording_path TEXT NOT NULL, recording_format VARCHAR(10) DEFAULT 'wav', file_size_bytes BIGINT, duration_seconds INTEGER, silence_percentage DECIMAL(5,2), caller_id VARCHAR(80), destination VARCHAR(80), queue_name VARCHAR(64), agent VARCHAR(64), direction VARCHAR(10), -- 'inbound', 'outbound', 'internal' disposition VARCHAR(20), recorded_at TIMESTAMP NOT NULL, compressed BOOLEAN DEFAULT FALSE, transcribed BOOLEAN DEFAULT FALSE, transcript_path TEXT, quality_score DECIMAL(3,1), created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, -- Indexes for common analytics queries CONSTRAINT unique_recording UNIQUE (uniqueid, recording_path) ); CREATE INDEX idx_recorded_at ON recording_metadata(recorded_at); CREATE INDEX idx_agent ON recording_metadata(agent); CREATE INDEX idx_queue ON recording_metadata(queue_name); CREATE INDEX idx_caller ON recording_metadata(caller_id); CREATE INDEX idx_disposition ON recording_metadata(disposition); CREATE INDEX idx_direction ON recording_metadata(direction);

Automated Metadata Extraction Script

#!/usr/bin/env python3 """ recording_indexer.py — Scans recordings, extracts metadata, populates database. Run every 15 minutes via cron. """ import os import re import wave import subprocess import psycopg2 from datetime import datetime from pathlib import Path DB_CONFIG = { 'host': 'localhost', 'database': 'asterisk', 'user': 'asterisk', 'password': 'your_password' } MONITOR_DIR = '/var/spool/asterisk/monitor' FILENAME_PATTERN = re.compile( r'(\d{8}-\d{6})-(\d+\.\d+)-(\d+)-(\d+)\.wav$' ) def get_wav_duration(filepath): """Get duration in seconds from WAV file.""" try: with wave.open(filepath, 'r') as wf: frames = wf.getnframes() rate = wf.getframerate() return frames / float(rate) except Exception: return None def detect_silence_percentage(filepath): """Detect percentage of silence in recording using sox.""" try: result = subprocess.run( ['sox', filepath, '-n', 'stats'], capture_output=True, text=True, timeout=30 ) # Parse sox stats for RMS level for line in result.stderr.split('\n'): if 'RMS lev dB' in line: rms = float(line.split()[-1]) # Very rough: if RMS < -40dB, significant silence if rms < -40: return 80.0 elif rms < -30: return 50.0 elif rms < -20: return 20.0 return 5.0 except Exception: return None def index_recordings(): conn = psycopg2.connect(**DB_CONFIG) cur = conn.cursor() indexed = 0 for root, dirs, files in os.walk(MONITOR_DIR): for filename in files: if not filename.endswith('.wav'): continue filepath = os.path.join(root, filename) # Skip already indexed cur.execute( "SELECT 1 FROM recording_metadata WHERE recording_path = %s", (filepath,) ) if cur.fetchone(): continue # Extract metadata from filename match = FILENAME_PATTERN.search(filename) if match: timestamp_str, uniqueid, caller, dest = match.groups() recorded_at = datetime.strptime(timestamp_str, '%Y%m%d-%H%M%S') else: # Fallback: use file modification time uniqueid = filename.replace('.wav', '') recorded_at = datetime.fromtimestamp(os.path.getmtime(filepath)) caller = dest = None duration = get_wav_duration(filepath) file_size = os.path.getsize(filepath) silence_pct = detect_silence_percentage(filepath) cur.execute(""" INSERT INTO recording_metadata (uniqueid, recording_path, file_size_bytes, duration_seconds, silence_percentage, caller_id, destination, recorded_at) VALUES (%s, %s, %s, %s, %s, %s, %s, %s) ON CONFLICT (uniqueid, recording_path) DO NOTHING """, (uniqueid, filepath, file_size, duration, silence_pct, caller, dest, recorded_at)) indexed += 1 conn.commit() cur.close() conn.close() print(f"Indexed {indexed} new recordings") if __name__ == '__main__': index_recordings()

Linking to CDR Data

The key join between recordings and CDR is the uniqueid field:

-- Join recordings with CDR for complete call context SELECT r.recording_path, r.duration_seconds, r.silence_percentage, c.src AS caller, c.dst AS destination, c.dcontext AS context, c.billsec AS billable_seconds, c.disposition, c.accountcode, c.userfield AS queue_name FROM recording_metadata r JOIN cdr c ON r.uniqueid = c.uniqueid WHERE r.recorded_at >= CURRENT_DATE - INTERVAL '7 days' ORDER BY r.recorded_at DESC;

Step 4: Essential Recording Analytics Queries

With recordings linked to CDR data, you can build powerful analytics dashboards.

Query 1: Daily Recording Volume and Storage

-- Daily recording stats: count, total duration, storage used SELECT DATE(recorded_at) AS day, COUNT(*) AS total_recordings, ROUND(SUM(duration_seconds) / 3600.0, 1) AS total_hours, ROUND(SUM(file_size_bytes) / 1073741824.0, 2) AS storage_gb, ROUND(AVG(duration_seconds), 0) AS avg_duration_sec, ROUND(AVG(silence_percentage), 1) AS avg_silence_pct FROM recording_metadata WHERE recorded_at >= CURRENT_DATE - INTERVAL '30 days' GROUP BY DATE(recorded_at) ORDER BY day DESC;

Query 2: Agent Recording Analysis

-- Per-agent recording metrics: identify coaching opportunities SELECT c.dstchannel AS agent_channel, SUBSTRING(c.dstchannel FROM 'SIP/(.+)-') AS agent, COUNT(*) AS calls_recorded, ROUND(AVG(r.duration_seconds), 0) AS avg_duration, ROUND(AVG(r.silence_percentage), 1) AS avg_silence_pct, SUM(CASE WHEN r.duration_seconds < 30 THEN 1 ELSE 0 END) AS short_calls, SUM(CASE WHEN r.silence_percentage > 50 THEN 1 ELSE 0 END) AS high_silence_calls, ROUND(AVG(c.billsec), 0) AS avg_billsec FROM recording_metadata r JOIN cdr c ON r.uniqueid = c.uniqueid WHERE r.recorded_at >= CURRENT_DATE - INTERVAL '7 days' AND c.disposition = 'ANSWERED' GROUP BY c.dstchannel, SUBSTRING(c.dstchannel FROM 'SIP/(.+)-') ORDER BY calls_recorded DESC;

Query 3: Recording Coverage Analysis

-- What percentage of calls are being recorded? -- Identifies gaps in recording coverage SELECT DATE(calldate) AS day, COUNT(*) AS total_calls, COUNT(r.id) AS recorded_calls, ROUND(COUNT(r.id)::DECIMAL / COUNT(*) * 100, 1) AS coverage_pct, COUNT(*) - COUNT(r.id) AS missing_recordings FROM cdr c LEFT JOIN recording_metadata r ON c.uniqueid = r.uniqueid WHERE c.calldate >= CURRENT_DATE - INTERVAL '7 days' AND c.disposition = 'ANSWERED' AND c.billsec > 5 GROUP BY DATE(calldate) ORDER BY day DESC;

Query 4: Silence Detection — Find Problematic Calls

-- Calls with excessive silence (potential quality issues) -- High silence = hold without music, dead air, or connection problems SELECT r.recorded_at, r.caller_id, r.destination, r.duration_seconds, r.silence_percentage, r.recording_path, c.disposition, c.userfield AS queue FROM recording_metadata r JOIN cdr c ON r.uniqueid = c.uniqueid WHERE r.silence_percentage > 40 AND r.duration_seconds > 60 AND r.recorded_at >= CURRENT_DATE - INTERVAL '7 days' ORDER BY r.silence_percentage DESC LIMIT 20;

Tired of guessing what's happening in your queues?

Astervis gives you 30+ real-time charts, operator KPIs, and CRM integration for your Asterisk PBX. Self-hosted. Install in 5 minutes. From $119/mo flat unlimited operators.

Try Free

Query 5: Hourly Recording Heatmap

-- Recording volume by hour and day of week -- Identifies peak periods and potential capacity issues SELECT EXTRACT(DOW FROM recorded_at) AS day_of_week, EXTRACT(HOUR FROM recorded_at) AS hour, COUNT(*) AS recordings, ROUND(AVG(duration_seconds), 0) AS avg_duration, ROUND(SUM(file_size_bytes) / 1048576.0, 0) AS storage_mb FROM recording_metadata WHERE recorded_at >= CURRENT_DATE - INTERVAL '30 days' GROUP BY EXTRACT(DOW FROM recorded_at), EXTRACT(HOUR FROM recorded_at) ORDER BY day_of_week, hour;

Step 5: AI-Powered Speech Analytics

Modern call recording analytics goes beyond metadata. AI-powered speech analytics can automatically transcribe recordings and extract insights like customer sentiment, topic detection, and compliance verification.

Architecture Overview

┌──────────────┐ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │ Asterisk │────>│ Storage │────>│ Transcribe │────>│ Analyze │ │ MixMonitor │ │ (WAV/Opus) │ │ (Whisper) │ │ (NLP/LLM) │ └──────────────┘ └──────────────┘ └──────────────┘ └──────────────┘ │ ┌──────┴──────┐ │ Dashboard │ │ (Metrics) │ └─────────────┘

Option 1: Self-Hosted Whisper Transcription

OpenAI's Whisper model is free, open-source, and runs on your own hardware:

#!/usr/bin/env python3 """ transcribe_recordings.py — Batch transcribe recordings using Whisper. Requires: pip install openai-whisper torch """ import whisper import json import os import psycopg2 from pathlib import Path # Load Whisper model (options: tiny, base, small, medium, large-v3) # 'small' is the best balance of speed and accuracy for telephony model = whisper.load_model("small") DB_CONFIG = { 'host': 'localhost', 'database': 'asterisk', 'user': 'asterisk', 'password': 'your_password' } TRANSCRIPT_DIR = '/var/spool/asterisk/monitor/transcripts' def transcribe_pending(): conn = psycopg2.connect(**DB_CONFIG) cur = conn.cursor() # Get un-transcribed recordings cur.execute(""" SELECT id, recording_path, recorded_at FROM recording_metadata WHERE transcribed = FALSE AND duration_seconds > 10 AND duration_seconds < 1800 ORDER BY recorded_at DESC LIMIT 50 """) for rec_id, rec_path, recorded_at in cur.fetchall(): if not os.path.exists(rec_path): continue try: # Transcribe with Whisper result = model.transcribe( rec_path, language=None, # Auto-detect language task="transcribe", fp16=False # Use fp32 for CPU ) # Save transcript transcript_path = os.path.join( TRANSCRIPT_DIR, recorded_at.strftime('%Y/%m/%d'), f"{os.path.basename(rec_path).replace('.wav', '.json')}" ) os.makedirs(os.path.dirname(transcript_path), exist_ok=True) transcript_data = { 'text': result['text'], 'language': result.get('language', 'unknown'), 'segments': [ { 'start': seg['start'], 'end': seg['end'], 'text': seg['text'] } for seg in result.get('segments', []) ] } with open(transcript_path, 'w') as f: json.dump(transcript_data, f, indent=2) # Update database cur.execute(""" UPDATE recording_metadata SET transcribed = TRUE, transcript_path = %s WHERE id = %s """, (transcript_path, rec_id)) conn.commit() print(f"Transcribed: {os.path.basename(rec_path)} " f"({result.get('language', '?')})") except Exception as e: print(f"Error transcribing {rec_path}: {e}") continue cur.close() conn.close() if __name__ == '__main__': transcribe_pending()

Hardware Requirements for Whisper

ModelVRAMCPU Time/Min AudioGPU Time/Min AudioAccuracy
tiny~1 GB~10 sec~1 secGood for keyword spotting
base~1 GB~15 sec~2 secAcceptable for analytics
small~2 GB~30 sec~3 secRecommended for telephony
medium~5 GB~60 sec~5 secHigh accuracy
large-v3~10 GB~120 sec~8 secBest accuracy

For a 50-agent call center processing 300 recordings/day averaging 5 minutes each, the small model on a GPU would process the entire day's recordings in about 75 minutes.

Option 2: Cloud Transcription API

For teams that prefer managed services:

# Using OpenAI Whisper API (cloud) import openai client = openai.OpenAI(api_key="your-api-key") def transcribe_cloud(audio_path): with open(audio_path, "rb") as audio_file: transcript = client.audio.transcriptions.create( model="whisper-1", file=audio_file, response_format="verbose_json", timestamp_granularities=["segment"] ) return transcript

Cloud API costs approximately $0.006 per minute of audio. For 300 calls × 5 min/day = $9/day = ~$270/month.

Keyword and Topic Detection

Once you have transcripts, you can detect keywords and topics without AI:

#!/usr/bin/env python3 """ keyword_analyzer.py — Detect keywords and topics in call transcripts. """ import json import re from collections import Counter # Define keyword categories relevant to your business KEYWORD_CATEGORIES = { 'complaint': [ 'complaint', 'unhappy', 'dissatisfied', 'terrible', 'worst', 'cancel', 'refund', 'manager', 'supervisor', 'unacceptable' ], 'upsell_opportunity': [ 'upgrade', 'additional', 'more features', 'enterprise', 'premium', 'advanced', 'expand', 'grow', 'scale' ], 'technical_issue': [ 'not working', 'broken', 'error', 'bug', 'crash', 'down', 'outage', 'slow', 'timeout', 'failed' ], 'positive': [ 'thank you', 'excellent', 'great', 'wonderful', 'perfect', 'appreciate', 'helpful', 'resolved', 'solved', 'happy' ], 'compliance_risk': [ 'guarantee', 'promise', 'definitely', 'always', 'never', 'lawsuit', 'legal', 'attorney', 'sue' ] } def analyze_transcript(transcript_path): with open(transcript_path) as f: data = json.load(f) text = data['text'].lower() results = {} for category, keywords in KEYWORD_CATEGORIES.items(): found = [] for keyword in keywords: count = len(re.findall(r'\b' + re.escape(keyword) + r'\b', text)) if count > 0: found.append({'keyword': keyword, 'count': count}) results[category] = { 'matches': found, 'total_hits': sum(m['count'] for m in found), 'flagged': len(found) > 0 } return results

Sentiment Timeline from Segments

# Simple sentiment scoring per transcript segment # For production: use a fine-tuned model or LLM API from textblob import TextBlob def segment_sentiment(transcript_path): """Analyze sentiment across call segments to detect escalation patterns.""" with open(transcript_path) as f: data = json.load(f) timeline = [] for segment in data.get('segments', []): blob = TextBlob(segment['text']) timeline.append({ 'start': segment['start'], 'end': segment['end'], 'text': segment['text'], 'polarity': round(blob.sentiment.polarity, 2), # -1 to 1 'subjectivity': round(blob.sentiment.subjectivity, 2) # 0 to 1 }) # Detect escalation: sentiment dropping over time if len(timeline) > 4: first_quarter = sum(s['polarity'] for s in timeline[:len(timeline)//4]) last_quarter = sum(s['polarity'] for s in timeline[-len(timeline)//4:]) escalation = first_quarter - last_quarter > 0.5 else: escalation = False return { 'segments': timeline, 'avg_polarity': round(sum(s['polarity'] for s in timeline) / max(len(timeline), 1), 2), 'escalation_detected': escalation }

Step 6: Compliance and Retention

Call recording regulations vary significantly by jurisdiction. Failing to comply can result in fines and legal liability.

ModelRequirementJurisdictions
One-party consentOne person on the call knowsUS (federal), UK, most of EU
Two-party/all-party consentEveryone on the call knowsCalifornia, Florida, Germany, some EU states
No consent neededBusiness calls exemptSome B2B contexts
; extensions.conf — Play recording notification before connecting [inbound-queue] exten => s,1,Answer() same => n,Playback(this-call-may-be-recorded) same => n,Wait(0.5) same => n,Macro(record-call) same => n,Queue(support,t,,,180) same => n,Hangup()

Automated Retention Policy

#!/bin/bash # recording-retention.sh — Enforce data retention policies # Run weekly via cron MONITOR_DIR="/var/spool/asterisk/monitor" TRANSCRIPT_DIR="$MONITOR_DIR/transcripts" # Retention periods (adjust per your compliance requirements) RECORDING_RETENTION_DAYS=365 # Keep recordings for 1 year TRANSCRIPT_RETENTION_DAYS=730 # Keep transcripts for 2 years COMPRESSED_RETENTION_DAYS=730 # Keep compressed archives for 2 years echo "$(date): Starting retention cleanup" # Delete original WAV files older than retention period find "$MONITOR_DIR" -name "*.wav" -mtime +$RECORDING_RETENTION_DAYS \ -not -path "*/compressed/*" -delete -print | wc -l | \ xargs -I {} echo "Deleted {} expired WAV files" # Delete compressed files older than retention period find "$MONITOR_DIR/compressed" -name "*.opus" \ -mtime +$COMPRESSED_RETENTION_DAYS -delete -print | wc -l | \ xargs -I {} echo "Deleted {} expired compressed files" # Delete transcripts older than retention period find "$TRANSCRIPT_DIR" -name "*.json" \ -mtime +$TRANSCRIPT_RETENTION_DAYS -delete -print | wc -l | \ xargs -I {} echo "Deleted {} expired transcripts" # Clean empty directories find "$MONITOR_DIR" -type d -empty -delete echo "$(date): Retention cleanup complete"

GDPR and Data Subject Requests

For GDPR compliance, you need the ability to find and delete all recordings for a specific caller:

-- Find all recordings for a specific caller (data subject request) SELECT recording_path, transcript_path, recorded_at, duration_seconds, destination FROM recording_metadata WHERE caller_id = '2125551234' ORDER BY recorded_at DESC; -- Delete all data for a specific caller (right to erasure) -- Step 1: Get file paths for physical deletion SELECT recording_path, transcript_path FROM recording_metadata WHERE caller_id = '2125551234'; -- Step 2: Delete database records DELETE FROM recording_metadata WHERE caller_id = '2125551234';

Step 7: Building the Dashboard

A recording analytics dashboard should surface insights without requiring manual review of individual recordings.

Dashboard Framework: Three Tiers

Tier 1 — Real-Time Wallboard (glanceable):

  • Active recordings in progress
  • Today's recording count vs target
  • Storage utilization percentage
  • Recording failure alerts

Tier 2 — Daily Operations (supervisors):

  • Recording coverage percentage (are all calls being recorded?)
  • Average call duration trends
  • Silence percentage distribution
  • High-silence call flags for review
  • Calls flagged by keyword detection

Tier 3 — Strategic Analytics (weekly/monthly):

  • Recording volume trends
  • Storage growth projections
  • Transcription insights: top topics, sentiment trends
  • Agent coaching opportunities (high silence, short calls)
  • Compliance audit: coverage gaps, consent verification

Key Metrics to Track

MetricFormulaTargetWhy It Matters
Recording CoverageRecorded calls / Total answered calls × 100>98%Compliance risk if calls aren't recorded
Avg Recording DurationTotal recording seconds / Total recordingsVariesTrend changes indicate process issues
Silence RatioSilence seconds / Total duration × 100<20%High silence = hold time, dead air
Storage VelocityGB added per dayPlan for 2xPrevents disk full emergencies
Transcription BacklogUn-transcribed / Total recordings<5%Ensures analytics are current
Negative Sentiment RateNegative calls / Total transcribed × 100<15%Customer satisfaction indicator
Compliance Flag RateFlagged calls / Total transcribed × 100<2%Risk management

Comparison: Recording Analytics Approaches

FeatureDIY ScriptsQueueMetricsCallCabinetAstervis
Setup effortHigh (weeks)Medium (days)Low (hours)Low (minutes)
Recording playbackManual file accessBuilt-in playerCloud playerBuilt-in player
CDR linkageCustom SQLAutomaticAutomaticAutomatic
Search by caller/dateSQL queriesGUI searchGUI searchInstant search
Speech analyticsBuild it yourselfNot includedAI-powered ($$$)Coming soon
Silence detectionCustom scriptingNot includedIncludedIncluded
Compliance toolsManualBasicFull suiteBuilt-in retention
Agent scoringNot includedManual QA formsAI scoringAutomated KPIs
CostFree + your timeCHF 8/agent/monthCustom pricingFrom $49/month
Self-hostedYesYesCloud onlyYes
Real-time dashboardBuild it yourselfBasicYes30+ charts

Quick-Start Checklist

Follow these steps to go from zero to call recording analytics:

  • Step 1: Enable recording — Add MixMonitor to your dialplan with structured filenames
  • Step 2: Organize storage — Set up date-based directories, plan capacity
  • Step 3: Link to CDR — Ensure CDR(recordingfile) is set, create metadata table
  • Step 4: Index recordings — Run the indexer script via cron every 15 minutes
  • Step 5: Compress older files — Set up daily compression cron job
  • Step 6: Build basic dashboard — Start with coverage and duration queries
  • Step 7: Add transcription — Install Whisper, start batch transcription
  • Step 8: Keyword detection — Define categories, scan transcripts automatically
  • Step 9: Set retention policy — Configure automated cleanup per compliance requirements
  • Step 10: Review and iterate — Weekly review of dashboard metrics, refine thresholds

What's Next

Building call recording analytics from scratch is powerful but time-consuming. You need to maintain scripts, manage storage, keep transcription pipelines running, and build dashboards — all while running your call center.

Astervis handles the heavy lifting. Install with a single command, and get instant access to 30+ analytics charts, call recording playback with CDR linkage, operator performance tracking, and real-time queue monitoring — all without writing a single SQL query.

Get started with a 14-day free trial: get.astervis.io


Looking for more Asterisk analytics guides? Check out our articles on monitoring Asterisk queues in real-time, CDR reporting best practices, and operator performance tracking.

Stop guessing. Start monitoring.

See your Asterisk call center's real performance — queue wait times, agent activity, trunk usage, and 30+ charts. Self-hosted on your server. Install in 5 minutes. No credit card required.

From $119/mo flat. Unlimited operators. 14-day free trial.

Share this article