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 Case | Without Analytics | With Analytics |
|---|---|---|
| Quality assurance | Random spot-checks (2-5% coverage) | Systematic scoring across 100% of calls |
| Agent coaching | Subjective feedback based on memory | Data-driven coaching with specific examples |
| Compliance verification | Manual review when complaints arise | Automated flagging of compliance gaps |
| Customer insights | Anecdotal observations | Trend analysis across thousands of interactions |
| Dispute resolution | Manual search through file system | Instant 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
| Feature | MixMonitor | Monitor |
|---|---|---|
| Audio mixing | Records both sides in one file | Separate files per direction (or mixed) |
| Performance impact | Lightweight (recommended) | Heavier, may cause audio issues |
| In-call control | Start/stop/pause dynamically | Limited control |
| Recording format | WAV, WAV49, GSM, SLN, SLIN | WAV, WAV49, GSM |
| Channel requirement | Works on answered channels | Works on answered channels |
| Recommended for | Production call centers | Legacy 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:
- —Navigate to Admin → Extensions → [Extension] → Recording
- —Set recording policy: Force, Don't Care, Yes, or No
- —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
| Agents | Avg Talk Hours/Day | Daily Storage | Monthly Storage | Annual Storage |
|---|---|---|---|---|
| 10 | 5 | 1.4 GB | 43 GB | 516 GB |
| 25 | 5 | 3.6 GB | 108 GB | 1.3 TB |
| 50 | 6 | 8.6 GB | 260 GB | 3.1 TB |
| 100 | 6 | 17.3 GB | 518 GB | 6.2 TB |
| 200 | 6 | 34.6 GB | 1 TB | 12.4 TB |
Recommended Storage Layout
/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
doneAdd to crontab:
# Run compression daily at 2 AM
0 2 * * * /usr/local/bin/compress-recordings.sh >> /var/log/recording-compression.log 2>&1Step 3: Link Recordings to CDR Metadata
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.
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
| Model | VRAM | CPU Time/Min Audio | GPU Time/Min Audio | Accuracy |
|---|---|---|---|---|
| tiny | ~1 GB | ~10 sec | ~1 sec | Good for keyword spotting |
| base | ~1 GB | ~15 sec | ~2 sec | Acceptable for analytics |
| small | ~2 GB | ~30 sec | ~3 sec | Recommended for telephony |
| medium | ~5 GB | ~60 sec | ~5 sec | High accuracy |
| large-v3 | ~10 GB | ~120 sec | ~8 sec | Best 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 transcriptCloud 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 resultsSentiment 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.
Recording Consent Models
| Model | Requirement | Jurisdictions |
|---|---|---|
| One-party consent | One person on the call knows | US (federal), UK, most of EU |
| Two-party/all-party consent | Everyone on the call knows | California, Florida, Germany, some EU states |
| No consent needed | Business calls exempt | Some B2B contexts |
Implementing Consent Announcements
; 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
| Metric | Formula | Target | Why It Matters |
|---|---|---|---|
| Recording Coverage | Recorded calls / Total answered calls × 100 | >98% | Compliance risk if calls aren't recorded |
| Avg Recording Duration | Total recording seconds / Total recordings | Varies | Trend changes indicate process issues |
| Silence Ratio | Silence seconds / Total duration × 100 | <20% | High silence = hold time, dead air |
| Storage Velocity | GB added per day | Plan for 2x | Prevents disk full emergencies |
| Transcription Backlog | Un-transcribed / Total recordings | <5% | Ensures analytics are current |
| Negative Sentiment Rate | Negative calls / Total transcribed × 100 | <15% | Customer satisfaction indicator |
| Compliance Flag Rate | Flagged calls / Total transcribed × 100 | <2% | Risk management |
Comparison: Recording Analytics Approaches
| Feature | DIY Scripts | QueueMetrics | CallCabinet | Astervis |
|---|---|---|---|---|
| Setup effort | High (weeks) | Medium (days) | Low (hours) | Low (minutes) |
| Recording playback | Manual file access | Built-in player | Cloud player | Built-in player |
| CDR linkage | Custom SQL | Automatic | Automatic | Automatic |
| Search by caller/date | SQL queries | GUI search | GUI search | Instant search |
| Speech analytics | Build it yourself | Not included | AI-powered ($$$) | Coming soon |
| Silence detection | Custom scripting | Not included | Included | Included |
| Compliance tools | Manual | Basic | Full suite | Built-in retention |
| Agent scoring | Not included | Manual QA forms | AI scoring | Automated KPIs |
| Cost | Free + your time | CHF 8/agent/month | Custom pricing | From $49/month |
| Self-hosted | Yes | Yes | Cloud only | Yes |
| Real-time dashboard | Build it yourself | Basic | Yes | 30+ 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.
