Every BPO running Asterisk faces the same invisible crisis: your clients demand proof that you're hitting SLA targets, your operations manager needs real-time visibility across campaigns, and your agents need instant feedback on performance. Yet most BPOs still rely on end-of-day spreadsheets or cobbled-together Grafana dashboards that took 200 hours to build.
This guide covers exactly how to build analytics infrastructure for an outsourced Asterisk call center — from multi-client SLA tracking to automated client reporting, agent performance scoring, and revenue optimization.
Why BPO Analytics Differs from In-House Call Centers
An in-house call center tracks metrics for one organization. A BPO juggles multiple clients, each with different:
| Dimension | In-House | BPO |
|---|---|---|
| SLA targets | One set of targets | Different per client contract |
| Queue configuration | Unified queues | Segregated queues per campaign |
| Agent allocation | Fixed teams | Shared agents across campaigns |
| Reporting | Internal dashboards | Client-facing reports + internal ops |
| Billing | N/A | Per-minute, per-call, or per-seat |
| Data isolation | Single tenant | Multi-tenant with strict boundaries |
| Compliance | One regulatory framework | Multiple (varies by client industry) |
This complexity creates unique analytics requirements that generic monitoring tools don't address. Most Asterisk monitoring solutions assume a single-tenant environment. BPOs need multi-dimensional analytics that segment everything by client, campaign, queue, and time period simultaneously.
The 12 KPIs Every BPO Must Track (and What They Actually Mean)
Client-Facing KPIs (What Goes in Client Reports)
These metrics directly impact client satisfaction and contract renewal:
| KPI | Formula | BPO Target | Why It Matters |
|---|---|---|---|
| Service Level | Calls answered within X seconds ÷ Total calls × 100 | Per contract (typically 80/20 or 80/30) | Primary SLA metric. Penalties for non-compliance. |
| Average Speed of Answer (ASA) | Total wait time ÷ Total calls answered | < 30 seconds | Client-facing quality indicator |
| Abandonment Rate | Abandoned calls ÷ Total inbound calls × 100 | < 5% | Revenue loss indicator |
| First Call Resolution (FCR) | Resolved on first call ÷ Total calls × 100 | > 70% | Cost efficiency and satisfaction |
| Average Handle Time (AHT) | (Talk time + Hold time + ACW) ÷ Total calls | Varies by campaign | Drives cost-per-call calculations |
| CSAT Score | Post-call survey average | > 4.0/5.0 | Client retention metric |
Operational KPIs (Internal BPO Management)
| KPI | Formula | BPO Target | Why It Matters |
|---|---|---|---|
| Agent Utilization | (Handle time ÷ Logged-in time) × 100 | 75-85% | Revenue per seat optimization |
| Schedule Adherence | Time in schedule ÷ Scheduled time × 100 | > 95% | Workforce planning accuracy |
| Cost Per Call | Total campaign cost ÷ Total calls handled | Varies | Profitability per contract |
| Revenue Per Seat Hour | Campaign revenue ÷ (Agents × Hours) | Track trend | Business health metric |
| Occupancy Rate | Handle time ÷ (Handle time + Available time) × 100 | 80-90% | Capacity planning |
| Shrinkage | Non-productive time ÷ Paid time × 100 | < 30% | True capacity indicator |
The BPO Profitability Formula
Margin per campaign = (Revenue per seat hour × Seats × Hours) - (Agent cost + Infrastructure + Overhead)
Track this daily. If a campaign drops below 15% margin, investigate immediately: either agent utilization is low, AHT is creeping up, or the contract needs renegotiation.
Multi-Client Queue Architecture in Asterisk
The foundation of BPO analytics is proper queue segmentation. Here's a production-ready architecture:
Queue Naming Convention
; Format: {client}_{campaign}_{type}
; Examples:
[acme_sales_inbound] ; Acme Corp - Sales - Inbound
[acme_support_inbound] ; Acme Corp - Support - Inbound
[globex_billing_inbound] ; Globex Inc - Billing - Inbound
[globex_collections_outbound] ; Globex Inc - Collections - Outbound
queues.conf — Multi-Client Setup
; === Client: Acme Corp ===
; Contract: 80/20 SLA, max 3 min wait
[acme_sales_inbound]
strategy = rrmemory
timeout = 15
retry = 5
maxlen = 50
servicelevel = 20
weight = 2
announce-frequency = 30
announce-holdtime = once
monitor-format = wav
eventwhencalled = vars
eventmemberstatus = yes
[acme_support_inbound]
strategy = leastrecent
timeout = 20
retry = 5
maxlen = 30
servicelevel = 30
weight = 1
; === Client: Globex Inc ===
; Contract: 80/30 SLA, max 5 min wait
[globex_billing_inbound]
strategy = rrmemory
timeout = 20
retry = 5
maxlen = 40
servicelevel = 30
weight = 1
[globex_collections_outbound]
strategy = linear
timeout = 25
retry = 3
maxlen = 0Agent Assignment with Penalties
; agents.conf or queue members
; Shared agents across campaigns with skill-based routing
; Agent 101: Primary Acme Sales, Secondary Globex Billing
member => SIP/101,1,Agent101,SIP/101 ; Acme Sales (penalty 1 = primary)
member => SIP/101,3,Agent101,SIP/101 ; Globex Billing (penalty 3 = overflow)
; Agent 102: Primary Globex, Secondary Acme
member => SIP/102,1,Agent102,SIP/102 ; Globex Billing (primary)
member => SIP/102,3,Agent102,SIP/102 ; Acme Sales (overflow)SQL Queries for Multi-Client Analytics
All these queries work with standard Asterisk CDR/queue_log stored in PostgreSQL (or MySQL with minor adjustments).
1. Real-Time SLA Dashboard by Client
-- Real-time SLA compliance per client (today)
WITH queue_clients AS (
SELECT
CASE
WHEN queuename LIKE 'acme_%' THEN 'Acme Corp'
WHEN queuename LIKE 'globex_%' THEN 'Globex Inc'
WHEN queuename LIKE 'initech_%' THEN 'Initech LLC'
ELSE 'Unknown'
END AS client,
queuename,
event,
data1::int AS wait_time,
time
FROM queue_log
WHERE time >= CURRENT_DATE
AND event IN ('CONNECT', 'ABANDON', 'EXITWITHTIMEOUT')
),
sla_calc AS (
SELECT
client,
COUNT(*) FILTER (WHERE event = 'CONNECT') AS answered,
COUNT(*) FILTER (WHERE event = 'CONNECT' AND wait_time <= 20) AS answered_in_sla,
COUNT(*) FILTER (WHERE event IN ('ABANDON', 'EXITWITHTIMEOUT')) AS abandoned,
COUNT(*) AS total_calls,
ROUND(AVG(wait_time) FILTER (WHERE event = 'CONNECT'), 1) AS avg_speed_answer,
MAX(wait_time) AS max_wait
FROM queue_clients
GROUP BY client
)
SELECT
client,
total_calls,
answered,
abandoned,
ROUND(100.0 * answered_in_sla / NULLIF(answered, 0), 1) AS sla_pct,
ROUND(100.0 * abandoned / NULLIF(total_calls, 0), 1) AS abandon_pct,
avg_speed_answer AS asa_seconds,
max_wait AS longest_wait
FROM sla_calc
ORDER BY client;2. Agent Performance Across Campaigns
-- Which agents are most productive across which campaigns?
SELECT
agent,
CASE
WHEN queuename LIKE 'acme_%' THEN 'Acme Corp'
WHEN queuename LIKE 'globex_%' THEN 'Globex Inc'
ELSE 'Other'
END AS client,
COUNT(*) FILTER (WHERE event = 'CONNECT') AS calls_handled,
ROUND(AVG(data2::int) FILTER (WHERE event = 'COMPLETECALLER'
OR event = 'COMPLETEAGENT'), 0) AS avg_talk_time,
ROUND(AVG(data1::int) FILTER (WHERE event = 'CONNECT'), 0) AS avg_wait_given,
COUNT(*) FILTER (WHERE event = 'RINGNOANSWER') AS missed_calls
FROM queue_log
WHERE time >= CURRENT_DATE - INTERVAL '7 days'
AND event IN ('CONNECT', 'COMPLETECALLER', 'COMPLETEAGENT', 'RINGNOANSWER')
GROUP BY agent, client
ORDER BY client, calls_handled DESC;3. Hourly Campaign Heatmap
-- Call volume and SLA by hour per campaign (for staffing optimization)
SELECT
EXTRACT(HOUR FROM time) AS hour,
queuename AS campaign,
COUNT(*) AS total_calls,
COUNT(*) FILTER (WHERE event = 'CONNECT') AS answered,
COUNT(*) FILTER (WHERE event = 'ABANDON') AS abandoned,
ROUND(100.0 * COUNT(*) FILTER (WHERE event = 'CONNECT' AND data1::int <= 20)
/ NULLIF(COUNT(*) FILTER (WHERE event = 'CONNECT'), 0), 1) AS sla_pct,
ROUND(AVG(data1::int) FILTER (WHERE event = 'CONNECT'), 0) AS asa
FROM queue_log
WHERE time >= CURRENT_DATE
AND event IN ('CONNECT', 'ABANDON', 'EXITWITHTIMEOUT')
GROUP BY hour, campaign
ORDER BY campaign, hour;4. Revenue and Profitability by Campaign
-- Campaign profitability analysis (requires billing config table)
WITH campaign_billing AS (
-- Replace with your actual billing rates
SELECT 'acme_sales_inbound' AS queue, 0.85 AS rate_per_minute, 'per_minute' AS model
UNION ALL
SELECT 'globex_billing_inbound', 1.20, 'per_minute'
UNION ALL
SELECT 'initech_support_inbound', 15.00, 'per_call'
),
call_stats AS (
SELECT
queuename,
COUNT(*) FILTER (WHERE event IN ('COMPLETECALLER', 'COMPLETEAGENT')) AS completed_calls,
SUM(data2::int) FILTER (WHERE event IN ('COMPLETECALLER', 'COMPLETEAGENT')) AS total_talk_seconds
FROM queue_log
WHERE time >= DATE_TRUNC('month', CURRENT_DATE)
AND event IN ('COMPLETECALLER', 'COMPLETEAGENT')
GROUP BY queuename
)
SELECT
cs.queuename AS campaign,
cs.completed_calls,
ROUND(cs.total_talk_seconds / 60.0, 0) AS total_minutes,
cb.model AS billing_model,
CASE
WHEN cb.model = 'per_minute' THEN ROUND(cs.total_talk_seconds / 60.0 * cb.rate_per_minute, 2)
WHEN cb.model = 'per_call' THEN cs.completed_calls * cb.rate_per_minute
END AS estimated_revenue
FROM call_stats cs
JOIN campaign_billing cb ON cs.queuename = cb.queue
ORDER BY estimated_revenue DESC;5. Client SLA Breach Alert
-- Detect campaigns at risk of SLA breach (rolling 30-minute window)
WITH rolling AS (
SELECT
queuename,
COUNT(*) FILTER (WHERE event = 'CONNECT' AND data1::int <= 20) AS in_sla,
COUNT(*) FILTER (WHERE event = 'CONNECT') AS answered,
COUNT(*) FILTER (WHERE event = 'ABANDON') AS abandoned
FROM queue_log
WHERE time >= NOW() - INTERVAL '30 minutes'
AND event IN ('CONNECT', 'ABANDON', 'EXITWITHTIMEOUT')
GROUP BY queuename
)
SELECT
queuename AS campaign,
ROUND(100.0 * in_sla / NULLIF(answered, 0), 1) AS current_sla,
abandoned,
CASE
WHEN 100.0 * in_sla / NULLIF(answered, 0) < 70 THEN 'CRITICAL'
WHEN 100.0 * in_sla / NULLIF(answered, 0) < 80 THEN 'WARNING'
ELSE 'OK'
END AS status
FROM rolling
WHERE answered > 0
ORDER BY current_sla ASC;Automated Client Reporting
BPO clients expect regular reports. Here's how to automate them:
Weekly Report Structure
Every client report should include these sections:
- —Executive Summary — SLA compliance, total calls, key highlights
- —Service Level Performance — Daily SLA chart, target vs actual
- —Call Volume Analysis — Hourly distribution, peak patterns, trends
- —Agent Performance — Top/bottom performers, training recommendations
- —Quality Metrics — FCR, CSAT, transfer rates
- —Recommendations — Data-driven suggestions for improvement
Generating Reports with SQL
-- Weekly executive summary for client report
SELECT
DATE(time) AS report_date,
COUNT(*) FILTER (WHERE event IN ('CONNECT', 'ABANDON')) AS total_offered,
COUNT(*) FILTER (WHERE event = 'CONNECT') AS total_answered,
COUNT(*) FILTER (WHERE event = 'ABANDON') AS total_abandoned,
ROUND(100.0 * COUNT(*) FILTER (WHERE event = 'CONNECT')
/ NULLIF(COUNT(*) FILTER (WHERE event IN ('CONNECT', 'ABANDON')), 0), 1) AS answer_rate,
ROUND(100.0 * COUNT(*) FILTER (WHERE event = 'CONNECT' AND data1::int <= 20)
/ NULLIF(COUNT(*) FILTER (WHERE event = 'CONNECT'), 0), 1) AS sla_pct,
ROUND(AVG(data1::int) FILTER (WHERE event = 'CONNECT'), 0) AS avg_wait_sec,
ROUND(AVG(data2::int) FILTER (WHERE event IN ('COMPLETECALLER', 'COMPLETEAGENT')), 0) AS avg_talk_sec
FROM queue_log
WHERE queuename LIKE 'acme_%'
AND time >= CURRENT_DATE - INTERVAL '7 days'
AND event IN ('CONNECT', 'ABANDON', 'COMPLETECALLER', 'COMPLETEAGENT')
GROUP BY DATE(time)
ORDER BY report_date;Bash Script for Automated PDF Reports
#!/bin/bash
# generate_client_report.sh — Run weekly via cron
# Requires: psql, python3, wkhtmltopdf
CLIENT="acme"
WEEK_START=$(date -d "last monday" +%Y-%m-%d)
WEEK_END=$(date -d "last sunday" +%Y-%m-%d)
REPORT_DIR="/var/reports/${CLIENT}/${WEEK_START}"
mkdir -p "$REPORT_DIR"
# Extract data
psql -h localhost -U asterisk -d asterisk_cdr -t -A -F',' \
-c "SELECT DATE(time), COUNT(*) FILTER (WHERE event='CONNECT'),
COUNT(*) FILTER (WHERE event='ABANDON'),
ROUND(AVG(data1::int) FILTER (WHERE event='CONNECT'),0)
FROM queue_log
WHERE queuename LIKE '${CLIENT}_%'
AND time BETWEEN '${WEEK_START}' AND '${WEEK_END}'
GROUP BY DATE(time) ORDER BY 1" \
> "${REPORT_DIR}/daily_stats.csv"
echo "Report generated: ${REPORT_DIR}"
# Convert to PDF, email to client contactFive Dashboards Every BPO Needs
1. Client SLA Wallboard (Real-Time)
Purpose: Large screen display showing live SLA status for all clients.
| Widget | Content | Update |
|---|---|---|
| Client SLA cards | Current SLA % with green/yellow/red | Every 10 seconds |
| Queue depth | Callers waiting per campaign | Real-time |
| Longest wait | Current max wait time | Real-time |
| Agent status grid | Available / On call / ACW / Offline | Real-time |
| Abandon ticker | Rolling 30-min abandon count | Every minute |
2. Campaign Operations Dashboard
Purpose: Operations manager view for cross-campaign resource allocation.
| Widget | Content |
|---|---|
| Campaign comparison table | SLA, ASA, AHT, utilization side-by-side |
| Agent allocation matrix | Which agents on which campaigns |
| Overflow alerts | Campaigns needing additional agents |
| Hourly forecast vs actual | Predicted vs actual call volume |
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.
3. Agent Leaderboard
Purpose: Real-time agent ranking to drive performance.
| Widget | Content |
|---|---|
| Top 10 agents (calls handled) | Ranked by volume today |
| Quality leaders | Highest FCR, shortest AHT |
| Coaching flags | Agents with high transfer rate or long ACW |
| Login/break tracking | Current status and time in each state |
4. Financial Dashboard
Purpose: CFO/CEO view of BPO profitability.
| Widget | Content |
|---|---|
| Revenue by campaign | MTD revenue per client |
| Cost per call trend | 30-day trend line |
| Margin by campaign | Revenue minus agent/infra cost |
| Utilization efficiency | Revenue per paid agent hour |
5. Client Portal Dashboard
Purpose: Read-only dashboard shared with clients via secure link.
| Widget | Content |
|---|---|
| SLA performance chart | Daily SLA for current month |
| Call volume trends | Hourly and daily patterns |
| Quality metrics | FCR, CSAT, AHT trends |
| Highlights | Automated commentary on changes |
Three Approaches to BPO Analytics on Asterisk
Approach 1: Build It Yourself (Grafana + PostgreSQL)
Effort: 120-200 hours initial setup, 10-20 hours/month maintenance
What you need:
- —PostgreSQL with CDR and queue_log tables
- —Grafana with PostgreSQL datasource
- —Custom SQL queries for each dashboard
- —Role-based access for client isolation
- —Scheduled report generation scripts
Pros: Full customization, no per-agent licensing cost Cons: Massive engineering time, fragile, no support, every new client needs custom work
Approach 2: QueueMetrics
Effort: 2-4 weeks setup, ongoing license cost
Cost: CHF 8/agent/month (50 agents = ~$480/month)
What you get: Queue monitoring, basic reporting, wallboard What you don't get: Multi-client revenue tracking, automated client reports, modern UI, real-time campaign comparison, easy agent reallocation
Pros: Industry standard, stable Cons: Expensive at scale, legacy Java UI, limited BPO-specific features, no built-in client portal
Approach 3: Astervis
Effort: 15 minutes to install, immediate results
Cost: From $49/month (10 agents) to $199/month (50 agents)
What you get:
- —30+ pre-built dashboards including queue heatmaps, agent performance, and trunk analytics
- —Real-time monitoring across all campaigns simultaneously
- —Operator management with KPI tracking and leaderboards
- —CRM integration (Bitrix24, AmoCRM) for lead tracking
- —Self-hosted — your data stays on your servers
- —One-command install:
curl -sSL https://get.astervis.io | bash
Why it works for BPOs:
- —Multi-queue visibility: See all client campaigns in one view
- —Agent performance tracking: Know who's excelling and who needs coaching
- —Cost efficiency: $199/month vs $480/month (QueueMetrics for 50 agents) or 200 hours of Grafana engineering
- —Self-hosted: Critical for BPOs with data residency requirements
- —14-day free trial: No credit card, no commitment
Comparison Matrix
| Feature | DIY Grafana | QueueMetrics | Astervis |
|---|---|---|---|
| Setup time | 120-200 hours | 2-4 weeks | 15 minutes |
| Multi-queue real-time | Custom build | Limited | ✅ Built-in |
| Agent performance | Custom SQL | Basic | ✅ 30+ charts |
| Heatmaps | Custom | ❌ | ✅ Built-in |
| CRM integration | Custom | ❌ | ✅ Bitrix24, AmoCRM |
| Cost (50 agents) | $0 + 200hr eng | ~$480/month | $199/month |
| Self-hosted | ✅ | ✅ | ✅ |
| Modern UI | Depends | ❌ Legacy Java | ✅ Modern web |
| Support | Community | Paid | Included |
| Maintenance | 10-20 hr/month | Low | Zero |
Common BPO Analytics Mistakes
1. Tracking Too Many Metrics
Problem: 50+ KPIs in client reports. Nobody reads them. Fix: 5-7 metrics per client report, aligned to their contract SLAs.
2. Reporting Lag
Problem: Client gets last week's report on Wednesday. Useless for real-time decisions. Fix: Real-time dashboards + automated daily email summaries + weekly detailed reports.
3. No Per-Campaign Cost Tracking
Problem: You know total costs but not which campaigns are profitable. Fix: Track agent time per queue using queue_log, calculate cost per handled call per campaign.
4. Shared Agent Blind Spots
Problem: Agent works across 3 campaigns but you only see aggregate stats. Fix: Track performance per queue per agent. Identify which campaigns each agent excels at.
5. Ignoring Abandoned Call Patterns
Problem: You report "5% abandonment" but don't analyze when and why. Fix: Track abandonment by hour, day, queue position, and wait time. Most abandons happen in specific patterns that are fixable with staffing adjustments.
6. Manual Client Reporting
Problem: Someone spends 4 hours every Friday building Excel reports for 10 clients. Fix: Automate SQL → template → PDF → email. Zero human time after setup.
Getting Started: BPO Analytics in 30 Minutes
Step 1: Audit Your Queue Structure (5 minutes)
Verify your queues follow a client_campaign_type naming convention. If not, rename them — this is the foundation of multi-client analytics.
asterisk -rx "queue show" | grep -E "^[a-z]"Step 2: Verify Data Collection (5 minutes)
Ensure queue_log and CDR are writing to your database:
# Check queue_log is flowing
tail -5 /var/log/asterisk/queue_log
# Check CDR database
psql -U asterisk -c "SELECT COUNT(*) FROM cdr WHERE calldate >= CURRENT_DATE"Step 3: Install Astervis (5 minutes)
curl -sSL https://get.astervis.io | bashFollow the setup wizard — it auto-detects your Asterisk configuration and begins importing data immediately.
Step 4: Configure Campaign Views (10 minutes)
Group your queues by client in Astervis. Set up agent assignments and SLA targets per campaign.
Step 5: Set Up Alerts (5 minutes)
Configure alerts for:
- —SLA dropping below contract threshold
- —Abandonment rate exceeding 5%
- —Queue depth exceeding maximum
- —Agent availability dropping below minimum
Key Takeaways
- —BPO analytics is fundamentally different from in-house — multi-tenant, multi-SLA, multi-billing model complexity requires purpose-built tools.
- —Track 12 core KPIs split between client-facing (SLA, ASA, FCR) and operational (utilization, cost per call, revenue per seat).
- —Queue naming convention is the foundation — use
client_campaign_typeformat for automatic segmentation. - —Automate client reporting — manual report generation doesn't scale past 3 clients.
- —Monitor profitability per campaign — knowing aggregate numbers hides underperforming contracts.
- —Astervis provides BPO-ready analytics in 15 minutes vs 200+ hours of DIY engineering, with 30+ dashboards, real-time monitoring, and CRM integration at $199/month for 50 agents.
Start your free 14-day trial at astervis.io — no credit card required. See every client campaign, every agent metric, and every SLA target in one dashboard within minutes.
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.
