Skip to main content

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!