Telecom Billing Software for ISPs and MVNOs: Rating, Invoicing, Build vs Buy
Founder & Lead Engineer, RAITHub
Most ISPs, WISPs and VoIP providers should buy their core billing platform and build only the part no vendor handles the way they sell. ISP platforms such as Sonar start at $1.25 per active subscriber a month. The part worth building is usually usage rating: CDRs for unusual bundles, wholesale or MVNO traffic. Connect it to the bought core through APIs.
This guide is for operators and engineering leads at ISPs, wireless ISPs (WISPs), small mobile virtual network operators (MVNOs) and VoIP providers, anywhere in the world. One thing up front: RAITHub has not shipped a telecom billing product. What follows is engineering guidance, grounded in billing and payments systems RAITHub has built for other industries, and in vendor documentation cited inline. It does not cover OSS/BSS integration, online charging system (OCS) integration or TM Forum conformance, none of which RAITHub has delivered.
What does telecom billing software actually have to do?
A telecom bill combines a recurring subscription with usage that arrives in bulk, often hours later, from network equipment you do not control. The system has six jobs:
- Usage rating. Turning a call detail record (CDR), the per-call or per-session log a switch or carrier produces, into a price. For VoIP that means matching the dialled number to a destination prefix, applying the rate and the billing increment. For data, it means summing bytes per session against an allowance.
- Plans and bundles. A 100 Mbps plan with a static IP add-on, 500 included minutes, and an equipment rental line. Bundles decide which usage is free and which is overage.
- Proration. Charging for part of a cycle when a customer upgrades, downgrades, moves house or is installed mid-month.
- Taxes and regulatory fees. VAT or GST in most countries; in the US, stacked federal, state and local telecom taxes and fees. This is a scope question, covered below.
- Invoicing. One document per customer per cycle that a customer can read and a support agent can explain line by line.
- Dunning. The sequence after a failed payment: retries, reminders, a grace period, then throttling or suspension of service, and reconnection once paid.
The first and last jobs are what make telecom different from ordinary SaaS billing. Usage arrives from a network, not your own app, and non-payment ends in a network action, not a disabled login. The billing patterns underneath (flat, tiered, usage and hybrid) are the same ones covered in SaaS billing models explained.
Should an ISP or VoIP provider build or buy billing software?
Buy the core, in most cases. ISP-specific platforms already bundle billing with the things an ISP needs next to it: RADIUS authentication, customer portals, ticketing and network monitoring. Splynx, for example, lists "Recurring & Prepaid billing", "Fees and tax management", "VoIP billing", a customer portal and a RADIUS server on its product page. Rebuilding all of that is rarely a good use of money.
| Option | Pricing model (from the vendor's page) | Good fit | What you still build |
|---|---|---|---|
| Splynx | Per active subscriber; minimum licence of 400 subscribers, in steps of 100. Prices come from a calculator, not a published list. | Fixed and wireless ISPs that want billing, RADIUS and a portal in one product | Integrations with your own systems; any rating logic outside the product |
| Sonar | $1.25 per active subscriber a month up to 5,000 subscribers, $1.00 to 10,000, $0.75 to 50,000; from $500 a month on an annual contract | ISPs that want scheduling, ticketing, portal and billing reports together | Custom reports, integrations, unusual products |
| Chargebee | Flow plan: 0.80% of invoicing volume with a $99 minimum, or $160 plus 0.65%; includes 100M usage events a month | VoIP or connectivity products sold like SaaS, often online, with card payments | CDR collection and rating before usage is sent; network provisioning and suspension |
| Stripe Billing | Pay-as-you-go at 0.7% of Billing volume, plus payment processing | Developer-led teams already on Stripe, simple plans plus metered usage | Rating, bundles and allowances before usage is reported; telecom tax handling; network actions |
| Custom build | Engineering cost, then hosting and maintenance | Wholesale or MVNO rating, unusual bundles, or a product no platform models | Everything, including the audit trail and reconciliation |
At 3,000 active subscribers, Sonar's first tier works out at $3,750 a month. At $150,000 of monthly invoicing, Stripe Billing's 0.7% is $1,050 a month and Chargebee's first Flow option is $1,200. Neither figure includes payment processing. Prices are from each vendor's pricing page on 1 October 2026, so check them again before you decide.
Stripe's own usage-based billing documentation now says Metronome, which is part of Stripe, is its "primary usage-based billing platform, recommended for all new integrations", with the Billing Meters API still "fully supported for existing integrations". If you pick Stripe for usage, read that page first.
When does a custom telecom billing build make sense?
When one of these is true, and a vendor demo confirms it:
- Your rating is the product. Wholesale VoIP termination with per-carrier rate decks, reseller margins, or an MVNO bundle mixing data, minutes and roaming that no platform prices correctly.
- You already have a billing platform and a gap. Rating CDRs in a small service of your own, then posting rated totals into the platform, is far less work than replacing the platform.
- Per-subscriber pricing stops making sense. Low-revenue prepaid lines can make a per-subscriber licence a large share of what each line earns. Run the numbers for your own base before assuming this.
Where you do build, TM Forum's published Open API specifications are a useful reference for the shape of the interfaces, even if you never seek conformance. TMF635 Usage Management covers "both rated and non-rated usage", and TMF678 Customer Bill covers "invoices produced for a customer". Borrowing their resource names makes a later move to a platform that supports them easier.
How do you rate CDRs without double-charging or losing money to rounding?
Two rules carry most of the weight. Ingestion must be idempotent, so a re-sent file or a retried batch changes nothing. Money must be integers, never floating point, and rounded once per invoice rather than once per call.
Start by keeping raw CDRs and rated results in separate tables. The raw row is evidence of what the network reported; the rated row is what you charged, and which rate version produced it.
CREATE TABLE cdr_raw (
id bigserial PRIMARY KEY,
source text NOT NULL, -- switch or carrier feed name
source_call_id text NOT NULL, -- the call ID the source assigned
account_id bigint NOT NULL,
destination text NOT NULL, -- dialled number, E.164 digits
started_at timestamptz NOT NULL,
duration_s integer NOT NULL CHECK (duration_s >= 0),
ingested_at timestamptz NOT NULL DEFAULT now(),
UNIQUE (source, source_call_id)
);
CREATE TABLE cdr_rated (
cdr_id bigint PRIMARY KEY REFERENCES cdr_raw (id),
rate_prefix text NOT NULL,
rate_version integer NOT NULL,
billed_s integer NOT NULL,
charge_micros bigint NOT NULL, -- millionths of the currency unit
invoice_id bigint -- set when the cycle closes
);
-- Ingestion: a duplicate from a re-sent file is skipped, not charged twice.
INSERT INTO cdr_raw (source, source_call_id, account_id, destination, started_at, duration_s)
VALUES ($1, $2, $3, $4, $5, $6)
ON CONFLICT (source, source_call_id) DO NOTHING;
The unique key on the source and its own call ID is what makes retries harmless. Do not key on your own generated ID: a re-sent file gets new IDs and every call is billed again.
Rating then picks the longest matching destination prefix, applies the minimum and the billing increment, and stores the charge in micros. Per-minute rates are often fractions of a cent, so whole cents per call would lose precision.
type Rate = {
prefix: string // e.g. '44' for the UK, '447' for UK mobiles
version: number
microsPerMinute: bigint // $0.012 per minute = 12_000n
minSeconds: number // minimum billable duration
incrementSeconds: number // 60 for per-minute, 1 for per-second
}
export function billableSeconds(durationS: number, rate: Rate): number {
if (durationS <= 0) return 0 // unanswered calls cost nothing
const s = Math.max(durationS, rate.minSeconds)
return Math.ceil(s / rate.incrementSeconds) * rate.incrementSeconds
}
export function rateCall(destination: string, durationS: number, rates: Rate[]) {
let best: Rate | undefined
for (const r of rates) {
if (destination.startsWith(r.prefix) && (!best || r.prefix.length > best.prefix.length)) best = r
}
// Never guess a price: park unmatched calls for review.
if (!best) return { status: 'unrated' as const }
const billedS = billableSeconds(durationS, best)
// Round up to the next micro, in integer arithmetic.
const chargeMicros = (BigInt(billedS) * best.microsPerMinute + 59n) / 60n
return { status: 'rated' as const, prefix: best.prefix, version: best.version, billedS, chargeMicros }
}
A worked example: a 125-second call to a prefix rated at $0.012 a minute. With a 60-second minimum and 60-second increments, it bills 180 seconds, or 36,000 micros ($0.036). With per-second billing it bills 125 seconds, or 25,000 micros ($0.025). The increment is a pricing decision, so put it in the rate table, not in code.
Round to minor units (cents, pence) once, when the invoice closes:
-- 1 cent = 10,000 micros. Integer division plus 5,000 rounds half up.
SELECT r.account_id,
(SUM(c.charge_micros) + 5000) / 10000 AS usage_minor_units
FROM cdr_rated c
JOIN cdr_raw r ON r.id = c.cdr_id
WHERE c.invoice_id = $1
GROUP BY r.account_id;
Rounding each call up to whole cents instead would charge the two calls above 4 cents and 3 cents, not 3.6 and 2.5. Over hundreds of thousands of calls, that is a visible overcharge, and one a customer can prove from their own records.
Three more rules that apply to any usage pipeline:
- Version your rates. Rate a call with the rate that was valid when it started, not the one valid when the file arrived. Store the version on the rated row.
- Decide what happens to late CDRs. Records for a closed cycle go on the next invoice as a dated line. Never reopen a sent invoice.
- Reconcile against the carrier. Compare your billed minutes per route with what the upstream carrier invoices you. A gap is either lost CDRs or a rating bug.
If rated usage then goes to Stripe, its recording usage guide requires meter event timestamps "within the past 35 calendar days", limits live mode to 1,000 meter event calls per second per account, and recommends pre-aggregating. Send per-account totals per hour, with an identifier built from the account and the hour, rather than one event per call.
How should plans, bundles and proration work for an ISP?
Model the bundle as data: a plan has recurring charges and allowances (minutes, gigabytes), and usage consumes the allowance before overage is rated. Keep allowances per billing period and per account, so a mid-month plan change can close the old allowance and open a new one.
For proration, pick one rule and print it on the plan page: day-based credit and charge on the next invoice is the most common and easiest to explain. Stripe's proration documentation shows the default behaviour if you bill through Stripe: prorations are created on a change but "positive prorations aren't immediately billed". Installations and moves add a telecom-specific wrinkle: billing should start when service is live, so tie the start date to the activation event, not to the order date.
What do telecom taxes add to the scope?
A lot, in some countries, which is why most operators use a tax engine or a platform that includes one, rather than hard-coding rates. In the US, for example, USAC explains that telecom carriers and interconnected VoIP providers contribute to the Universal Service Fund based on interstate and international end-user revenues, that the contribution factor "changes each quarter", and that carriers that pass it on must follow FCC rules on how the charge is calculated. State and local taxes sit on top. Tax engines such as Avalara's communications tax product exist for this "multijurisdictional" problem.
For a build, treat tax as an interface: the billing system sends the taxable lines, customer location and product codes, and stores what the engine returns. This is general information, not tax advice; confirm what applies to your services and your customers' locations with your tax adviser and your regulator.
How should dunning and suspension work?
Dunning in telecom ends in a network action, so the billing system and the network must agree on state. A workable sequence: retry the payment, email and SMS reminders, a grace period, then throttle or suspend, with reconnection as soon as a payment clears. The billing side should emit a single event, such as account suspended or account restored, that provisioning acts on. Log every state change with who or what caused it, as in a SaaS audit log, because disputed disconnections are where support time goes.
Test the whole cycle with simulated time before launch, including a payment that succeeds during the grace period. The approach is in testing payments and webhooks.
Why RAITHub for this
RAITHub has no telecom client to show you, and says so. What it has built is the billing machinery underneath:
- Recurring payments and scheduled jobs. PropDesk, a property management platform RAITHub built, collects recurring rent through Stripe, runs 5 scheduled jobs and has 1,024 automated tests. Monthly cycles, retries and reminders are the same shape as an ISP's billing run.
- Staff permissions. Sundor Skin, a B2B wholesale platform, has 12 staff roles, 88 permission codes and PostgreSQL row-level security. Billing systems need exactly that split between support, finance and admin.
- Protecting ingestion endpoints. This site runs rate limiting without Redis, a pattern that also suits a CDR upload or webhook endpoint.
The likely engagement is a rating and integration service next to a platform you buy, delivered as a fixed-scope API and backend project after a free 15-minute technical audit. See RAITHub's work for the case studies above.
When you don't need us
- A standard ISP with standard plans. Splynx, Sonar or a similar platform will do it, with a portal and RADIUS included.
- You need OCS or real-time charging. Real-time credit control for mobile data is a specialist field. Use a vendor with that product; RAITHub has not built one.
- You need BSS integration or TM Forum conformance. Choose a vendor with proven delivery in that area.
Have a rating gap your billing platform cannot close? Send RAITHub a sample of your CDRs and rate deck, and book the free audit.
Last reviewed: 1 October 2026. Vendor prices and Stripe behaviour from each vendor's pages on that date.
Frequently asked questions
What is telecom billing software?
Software that charges telecom customers for recurring plans and for usage. It rates call and data records, applies bundles, proration and taxes, produces invoices, collects payment and handles non-payment through reminders and suspension.
What is CDR rating?
Turning a call detail record into a charge. The system matches the dialled number to the longest destination prefix in a rate table, applies the minimum duration and billing increment, and multiplies by the rate for that prefix.
How much does ISP billing software cost?
It depends on the vendor. Sonar's pricing page lists $1.25 per active subscriber a month up to 5,000 subscribers, from $500 a month on an annual contract. Splynx prices per active subscriber from a 400-subscriber minimum through a calculator.
Can Stripe Billing handle telecom usage billing?
It can bill metered usage, but it does not rate CDRs. You rate calls or sessions yourself, then send aggregated usage. Stripe recommends Metronome for new usage-based integrations, and telecom taxes and network suspension stay outside Stripe.
Why store money as integers in a billing system?
Floating-point numbers cannot represent most decimal amounts exactly, so totals drift. Store charges as integers in a small unit, such as millionths of the currency, and round to cents once per invoice.
How do I stop duplicate CDRs from being billed twice?
Put a unique constraint on the source and the call ID the source assigned, and insert with ON CONFLICT DO NOTHING. A re-sent file or retried batch then adds nothing.
Related posts
Ready to discuss your project?
Book a free 15-minute technical audit with our engineering team.