VoIP Billing & CDR ProcessingReal-Time Rating Architecture at Scale
Design patterns for high-throughput CDR ingestion, real-time LCR rating engines, prepaid balance deduction, fraud detection, and database architecture for production VoIP billing systems.
5M+
CDRs/day with Flink
<100ms
rating latency
95%
fraud auto-caught
LPM
tariff lookup method
By Kaushik Parmar · Founder & VoIP Architect, CelloIP Technologies · January 17, 2026 · 11 min read
How does real-time VoIP billing work?
VoIP billing ingests CDRs from switches via Kafka, rates each CDR against a tariff table using Longest Prefix Match, deducts prepaid balances atomically, and runs fraud detection rules in real-time to block IRSF and similar attacks.
Table of Contents
CDR: The Foundation of VoIP Billing
Source Number
A-leg caller number (CLI)
Destination Number
Dialled number (DNIS)
Start / Answer / End Time
3 timestamps for accurate billing
Duration (seconds)
Billable call duration
Codec Used
G.711, G.722, Opus — affects quality billing
SIP Response Code
200 OK, 404, 486 — call disposition
Every billing operation — rating, invoicing, fraud detection, dispute resolution — depends on accurate CDR data. The billing architecture must: (1) ingest CDRs reliably at high volume, (2) rate each CDR in real time, (3) deduct from prepaid balances atomically, and (4) detect anomalies before fraudulent calls complete.
CDR Ingestion Architecture by Volume
Direct MySQL INSERT from switch
Switch → RabbitMQ → rating worker
Switch → Kafka → distributed rater
Switch → Kafka → Flink stream processor
Real-Time LCR Rating: Longest Prefix Match
Rating matches each CDR's destination number against a tariff table using Longest Prefix Match (LPM). A call to +447911123456 might match +44 (UK), +4479 (Vodafone UK), or +44791 — the longest match wins.
import redis
r = redis.Redis()
def get_rate(destination: str) -> float:
"""Longest-prefix-match tariff lookup using Redis."""
# Try progressively shorter prefixes
for length in range(len(destination), 2, -1):
prefix = destination[:length]
rate = r.hget('tariffs', prefix)
if rate:
return float(rate)
return float(r.hget('tariffs', 'default') or 0.05)
def max_call_duration(balance: float, destination: str) -> int:
"""Return max seconds a prepaid call can run."""
rate_per_min = get_rate(destination)
if rate_per_min <= 0:
return 3600 # unlimited for zero-rate destinations
return int((balance / rate_per_min) * 60)
# Example: balance $5.00, rate to UK mobile = $0.08/min
# max_call_duration(5.00, "+447911123456") → 3750 seconds (62.5 min)Fraud Detection: Automated Rules
VoIP fraud costs the industry $1.2B+ annually. IRSF (International Revenue Share Fraud) and PBX hacking are the most common attack vectors. These automated rules catch 95% of fraud.
Velocity Check
CRITICALMore than 5 simultaneous calls from one account → hold
Destination Anomaly
HIGHCalls to high-risk prefixes (+269, +676, +881) above threshold
Duration Anomaly
HIGHCalls over 60 minutes to non-whitelisted numbers
Time-of-Day
MEDIUMCalls outside business hours from account with no night-hours history
Geographic Jump
HIGHCall from US, then from UAE 5 minutes later — impossible travel
New Account Spike
CRITICALAccount created today making 50+ calls — likely compromised
Database Architecture for Billing
MySQL / PostgreSQL — Operational
ClickHouse — Analytics
Open-Source VoIP Billing Comparison
| System | Best For | Status | Licence | Rating |
|---|---|---|---|---|
| A2Billing | Asterisk-specific deployments | Mature | GPL | ★★★★☆ |
| CGRateS | Carrier-grade, FreeSWITCH/Kamailio | Modern | GPL | ★★★★★ |
| PortaBilling | Enterprise / ISP | Commercial | Proprietary | ★★★★★ |
| Custom (CelloIP) | Complex requirements, multi-currency | Full-custom | Owned by you | ★★★★★ |
Frequently Asked Questions
QWhich database is best for CDR storage?
For billing operations: PostgreSQL or MySQL with monthly partitioning. For real-time analytics: ClickHouse (columnar, extremely fast aggregate queries over billions of rows). Use both: MySQL for billing, ClickHouse for reporting.
QHow do you handle CDR loss during switch restart?
Configure your switch to write CDRs to local disk first (cdr_csv or cdr_sqlite), then a separate process tails and ships them to the queue. This prevents loss during network partition or message queue downtime.
QWhat is IRSF fraud and how do you prevent it?
International Revenue Share Fraud (IRSF) involves attackers routing calls to premium-rate numbers and collecting the revenue share. Prevent it with velocity checks, destination prefix blocking, and spending limits that trigger holds above threshold.
QHow do you implement mid-call balance checks?
Calculate maximum duration from current balance and rate when the call starts. Set a channel hangup command via FreeSWITCH ESL or Asterisk AMI at that maximum duration. Check balance every 60 seconds for long calls.
Related Articles
Custom VoIP Billing Development
CelloIP Technologies builds custom VoIP billing engines — from real-time rating systems to multi-tenant invoicing platforms. When packaged solutions don't meet your requirements, we build it right.