Set Up a Call Center
Build a cloud call center with IVR menus, call routing, recording, and agent dashboard in 30 minutes.
What You'll Build
- ✅ IVR menu system
- ✅ Call routing to agents
- ✅ Call recording
- ✅ Queue management
- ✅ Agent dashboard
- ✅ Real-time analytics
Estimated Time: 30 minutes
Prerequisites
- ✅ AfriRoute Account with Voice API
- ✅ Node.js 18+ or Python 3.8+
- ✅ Voice-enabled phone number
System Architecture
Customer Call → IVR Menu → Queue → Agent
↓
Call Recording & Analytics
Step 1: IVR Menu (Node.js)
ivr-handler.js
const express = require('express');
const { AfriRoute } = require('afriroute-sdk');
require('dotenv').config();
const app = express();
app.use(express.json());
const client = new AfriRoute({
apiKey: process.env.AFRIROUTE_API_KEY,
apiSecret: process.env.AFRIROUTE_API_SECRET
});
// Handle incoming call
app.post('/voice/incoming', async (req, res) => {
const { call_id, from } = req.body;
console.log(`📞 Incoming call from ${from}`);
// Play welcome message and menu
const response = {
actions: [
{
action: 'speak',
text: 'Welcome to ACME Support. For sales, press 1. For support, press 2. For billing, press 3.',
language: 'en-US',
voice: 'female'
},
{
action: 'gather',
num_digits: 1,
timeout: 5,
callback_url: `${process.env.BASE_URL}/voice/menu`
}
]
};
res.json(response);
});
// Handle menu selection
app.post('/voice/menu', async (req, res) => {
const { digits, call_id, from } = req.body;
console.log(`Menu selection: ${digits}`);
let response;
switch (digits) {
case '1': // Sales
response = routeToQueue('sales', call_id, from);
break;
case '2': // Support
response = routeToQueue('support', call_id, from);
break;
case '3': // Billing
response = routeToQueue('billing', call_id, from);
break;
default:
response = {
actions: [
{
action: 'speak',
text: 'Invalid selection. Please try again.'
},
{
action: 'redirect',
url: `${process.env.BASE_URL}/voice/incoming`
}
]
};
}
res.json(response);
});
function routeToQueue(department, callId, from) {
// Add to queue
const position = addToQueue(department, { callId, from });
return {
actions: [
{
action: 'speak',
text: `Please hold while we connect you to ${department}. You are number ${position} in the queue.`
},
{
action: 'play',
url: 'https://example.com/hold-music.mp3',
loop: true
},
{
action: 'enqueue',
queue_name: department,
wait_url: `${process.env.BASE_URL}/voice/wait`,
max_wait_time: 300
}
]
};
}
const queues = {
sales: [],
support: [],
billing: []
};
function addToQueue(department, caller) {
queues[department].push(caller);
notifyAvailableAgents(department);
return queues[department].length;
}
app.listen(3000, () => {
console.log('📞 Call center running on port 3000');
});
Step 2: Agent Dashboard
agent-dashboard.js
const express = require('express');
const { AfriRoute } = require('afriroute-sdk');
const app = express();
class Agent {
constructor(id, name, skills) {
this.id = id;
this.name = name;
this.skills = skills;
this.status = 'available'; // available, busy, offline
this.currentCall = null;
}
acceptCall(call) {
this.status = 'busy';
this.currentCall = call;
console.log(`Agent ${this.name} accepted call from ${call.from}`);
}
endCall() {
this.status = 'available';
this.currentCall = null;
}
}
// Create agents
const agents = [
new Agent('agent1', 'John Doe', ['sales', 'support']),
new Agent('agent2', 'Jane Smith', ['support']),
new Agent('agent3', 'Bob Johnson', ['billing'])
];
function findAvailableAgent(department) {
return agents.find(agent =>
agent.status === 'available' &&
agent.skills.includes(department)
);
}
function notifyAvailableAgents(department) {
const agent = findAvailableAgent(department);
if (agent && queues[department].length > 0) {
const call = queues[department].shift();
connectCall(call, agent);
}
}
async function connectCall(call, agent) {
try {
agent.acceptCall(call);
// Bridge call to agent
await client.voice.connect({
call_id: call.callId,
to: agent.phone,
record: true,
recording_url: `${process.env.BASE_URL}/recordings`
});
console.log(`✅ Connected ${call.from} to ${agent.name}`);
} catch (error) {
console.error('Connection failed:', error);
agent.endCall();
}
}
// Agent dashboard API
app.get('/dashboard/agents', (req, res) => {
res.json({
agents: agents.map(a => ({
id: a.id,
name: a.name,
status: a.status,
current_call: a.currentCall
})),
queues: {
sales: queues.sales.length,
support: queues.support.length,
billing: queues.billing.length
}
});
});
app.listen(3001, () => {
console.log('📊 Dashboard running on port 3001');
});
Step 3: Call Recording
recording-handler.js
app.post('/voice/recording', async (req, res) => {
const { call_id, recording_url, duration } = req.body;
console.log(`🎥 Recording available: ${recording_url}`);
// Save to database
await saveRecording({
callId: call_id,
url: recording_url,
duration: duration,
timestamp: new Date()
});
res.status(200).send('OK');
});
async function saveRecording(recording) {
// Save to database
console.log('Recording saved:', recording);
}
Step 4: Python Implementation
call_center.py
from flask import Flask, request, jsonify
from afriroute import AfriRoute
import os
app = Flask(__name__)
client = AfriRoute(
api_key=os.getenv('AFRIROUTE_API_KEY'),
api_secret=os.getenv('AFRIROUTE_API_SECRET')
)
queues = {
'sales': [],
'support': [],
'billing': []
}
@app.route('/voice/incoming', methods=['POST'])
def incoming_call():
data = request.json
call_id = data['call_id']
from_number = data['from']
print(f"📞 Call from {from_number}")
return jsonify({
'actions': [
{
'action': 'speak',
'text': 'Welcome. Press 1 for sales, 2 for support, 3 for billing.',
'language': 'en-US'
},
{
'action': 'gather',
'num_digits': 1,
'timeout': 5,
'callback_url': f"{os.getenv('BASE_URL')}/voice/menu"
}
]
})
@app.route('/voice/menu', methods=['POST'])
def menu_selection():
data = request.json
digits = data['digits']
departments = {
'1': 'sales',
'2': 'support',
'3': 'billing'
}
department = departments.get(digits)
if department:
return route_to_department(department, data['call_id'], data['from'])
else:
return jsonify({
'actions': [
{'action': 'speak', 'text': 'Invalid selection'},
{'action': 'redirect', 'url': '/voice/incoming'}
]
})
def route_to_department(dept, call_id, from_number):
queues[dept].append({'call_id': call_id, 'from': from_number})
position = len(queues[dept])
return jsonify({
'actions': [
{
'action': 'speak',
'text': f'Connecting to {dept}. Position {position} in queue.'
},
{
'action': 'enqueue',
'queue_name': dept,
'max_wait_time': 300
}
]
})
if __name__ == '__main__':
app.run(port=3000, debug=True)
Step 5: Dashboard Frontend
dashboard.html
<!DOCTYPE html>
<html>
<head>
<title>Call Center Dashboard</title>
<style>
body { font-family: Arial; padding: 20px; }
.agent { border: 1px solid #ddd; padding: 15px; margin: 10px; border-radius: 5px; }
.available { background: #e8f5e9; }
.busy { background: #ffebee; }
.queue { background: #e3f2fd; padding: 15px; margin: 10px; border-radius: 5px; }
</style>
</head>
<body>
<h1>📞 Call Center Dashboard</h1>
<div id="agents"></div>
<div id="queues"></div>
<script>
async function loadDashboard() {
const res = await fetch('/dashboard/agents');
const data = await res.json();
// Display agents
const agentsHtml = data.agents.map(agent => `
<div class="agent ${agent.status}">
<h3>${agent.name}</h3>
<p>Status: ${agent.status}</p>
${agent.current_call ? `<p>On call with: ${agent.current_call.from}</p>` : ''}
</div>
`).join('');
document.getElementById('agents').innerHTML = agentsHtml;
// Display queues
const queuesHtml = Object.entries(data.queues).map(([dept, count]) => `
<div class="queue">
<h3>${dept}</h3>
<p>Waiting: ${count}</p>
</div>
`).join('');
document.getElementById('queues').innerHTML = queuesHtml;
}
// Refresh every 2 seconds
setInterval(loadDashboard, 2000);
loadDashboard();
</script>
</body>
</html>
Best Practices
- Queue Management: Prioritize calls by wait time
- Agent Skills: Route based on expertise
- SLA Monitoring: Track answer times
- Call Recording: Store for quality assurance
- Analytics: Monitor metrics (answer rate, duration)
Troubleshooting
- Calls not routing: Check agent availability
- Recording fails: Verify storage configuration
- Long wait times: Add more agents
Next Steps
Pro Tip
Monitor queue length and add agents dynamically during peak hours!