System Overview
CellHub Billing is a complete VoIP billing and softswitch platform built on Laravel 12, Asterisk 20 (PJSIP), and Janus WebRTC. It is a multi-tenant SaaS application that supports admin, reseller, and client roles, suitable for ITSPs, call centers, and businesses requiring VoIP infrastructure with real-time billing.
Key capabilities
- Real-time per-second billing with rate decks and tariffs
- Trunk groups and Least Cost Routing (LCR) with sequential failover
- Built-in WebRTC web phone (Janus) — no external softphone needed
- Campaign manager (Manual / Auto Agent / IVR / IVR + Live Transfer)
- Click-to-Call widget for client websites
- DIDs, IVR builder, queues, holidays, audio files
- SIP Trace, CDR reports, summary reports by day/month/trunk/user/DID
- Auto update system with one-click upgrades
User Roles
CellHub Billing has four roles, each with different access levels:
| Role | Access |
|---|---|
| SUPERADMIN | Full system access, including resellers, group users, billing settings, and infrastructure. |
| ADMIN | Manages clients, SIP users, rates, trunks, DIDs, and most settings. Cannot manage other admins or system-level configuration. |
| RESELLER | Manages own clients only (scoped). Has its own rate decks/margins. Sees its own customer base, CDRs, and revenue. |
| CLIENT | End user of VoIP service. Manages own SIP user(s), uses Web Phone, accesses Click-to-Call widget. Cannot create/delete SIP users. |
Multi-Tenancy
The system separates data by ownership:
- Admins see all data across the entire system.
- Resellers see only their own users and their users' data (CDRs, SIP users, balances).
- Clients see only their own data.
Internally this is enforced through:
- Database
user_id/reseller_idforeign keys - Eloquent scope
User::forCurrentUser()applied to most queries - Controller-level
abort(403)checks for sensitive operations
First Steps After Installation
- Login — Use admin credentials provided during installation.
- Configure SMTP — Settings → SMTP. Required for password reset, low credit alerts, system notifications.
- Add a Trunk — Routes → Trunks. This is the upstream provider you route calls through.
- Create a Rate Deck — Rates → Rate Decks. Or upload a CSV of prefix/rate pairs.
- Create a Plan — Rates → Plans. Assigns the rate deck and any per-minute markup.
- Create a User (Client) — Clients → Users. Assign the plan and add credit.
- Create a SIP User — Clients → SIP Users. Owned by the client. Test with a softphone.
- Make a test call — Use a softphone or the built-in Web Phone.
Dashboard
The first page after login. Displays key statistics tailored to the current role.
Sections
- Active calls — How many calls are happening right now.
- Today\'s totals — Calls, duration, revenue.
- Top destinations — Most-called countries today.
- Recent CDRs — Last 10 calls.
- System health (admin) — Asterisk, Janus, MySQL, queue worker status.
Users
ADMIN RESELLER
Users are customer accounts. Each user can own multiple SIP users.
Fields
| Field | Description |
|---|---|
| Name / Email | Login credentials and display name |
| Role | admin, reseller, or client |
| Plan | Pricing plan (rates) the user is billed against |
| Credit | Current prepaid balance — calls deduct from this |
| SIP account limit | Maximum SIP users this account can have (0 = unlimited) |
| Status | active / suspended / blocked |
| Reseller | If client belongs to a reseller, set here |
How to add credit
Open user → "Add Credit" → enter amount and reference note. Balance increases immediately, recorded in the credits table.
SIP Users
SIP users are the actual SIP accounts that register to Asterisk and place calls. Each SIP user belongs to a User (owner).
Important fields
| Field | Description |
|---|---|
| Username / Password | SIP authentication credentials |
| Caller ID | Outbound caller ID, format: "Name" <number> |
| Host | dynamic for register-based, or a fixed IP for IP-to-IP downstream peer |
| Strip Prefix | Remove this prefix from incoming numbers (normalization) |
| Add Prefix | Prepend this to incoming numbers (normalization) |
| NAT | NAT traversal mode (default: force_rport,comedia) |
| Codecs | Allowed codecs (e.g., alaw,ulaw,g729) |
| Trunk Group / Single Trunk | Override default routing — if set, calls from this SIP user use this trunk/group instead of rate-based selection |
| Rate Deck | Override the user\'s plan rate deck for this SIP user only (rare) |
| Max Calls | Concurrent call limit per SIP user |
| Record Calls | If enabled, audio is recorded to /var/spool/asterisk/monitor |
Status indicator
The "SIP Status" column shows real-time registration: Online (green) means registered; Offline (red) means not registered. Updated via AJAX call to AMI.
Web Phone Access
ADMIN RESELLER — Located under Clients menu
Controls which SIP users have access to the built-in Web Phone (Janus WebRTC).
Toggle the "Web Phone Enabled" switch per SIP user. When enabled, that SIP user can log into the Dialer → Web Phone page and place calls from the browser.
Calls Online
ADMIN RESELLER
Real-time list of ongoing calls. Shows source, destination, trunk, duration so far, and SIP user.
You can force-hangup any call from this page (red trash icon).
Auto-refreshes every 5 seconds.
CallerID
Caller ID whitelist or blacklist (optional). Use cases:
- Whitelist: only allow calls from specific numbers
- Blacklist: block calls from specific numbers
If empty, no caller ID filtering is applied.
Restricted Numbers
Per-SIP-user destination blacklist. If a SIP user attempts to call a number matching a restricted prefix, the call is rejected at the AGI billing layer with BILLINGRESULT=blocked.
Two layers
- Per-SIP-user: Restricted Numbers entries are tied to specific SIP users
- Global: The
restrict_phonestable applies to all calls regardless of SIP user
Match is prefix-based: dst LIKE CONCAT(prefix, '%').
User History
Audit log of changes to user accounts: credit additions, plan changes, status changes, etc.
IAX Users
For older clients using IAX2 protocol (Inter-Asterisk eXchange). Most modern setups use SIP only.
Sipuras
Provisioning records for Linksys/Sipura ATA devices. Used to auto-generate config files for physical ATAs.
CDR Reports
Call Detail Records — every completed call has a CDR row. The main reporting interface.
Columns
| Column | Meaning |
|---|---|
| Date | Call start time |
| SIP User | The SIP user who made the call |
| Source | Caller ID number |
| Destination | Dialed number |
| Dest. Name | Destination country/network (looked up from prefix) |
| Duration | Total call duration including ringing |
| Bill Sec | Billable seconds (after answer) |
| Status | ANSWERED / NO ANSWER / BUSY / FAILED / CONGESTION |
| Code | SIP hangup cause code |
| Cost | Charged amount in account currency |
Filters
Filter by date range, SIP user, destination prefix, status. Click "Filter" to apply.
Export
"Export" button downloads filtered CDRs as CSV.
Bulk delete
ADMIN RESELLER
Each row has a checkbox. Select multiple rows individually or hold Shift and click to select a range. The bulk action bar appears at the top with a "Delete Selected" button. Reseller scope: only own users' CDRs are deletable.
Failed Calls
Filtered view of CDRs where disposition != ANSWERED. Helpful for diagnosing routing issues, busy destinations, or trunk failures.
Trunk Errors
Aggregated view of CONGESTION/CHANUNAVAIL responses by trunk. If a trunk has an unusually high error rate, this is where you spot it.
Call Archive
Recorded call audio files. Each row shows the recording, with an inline player and download link.
Recording is enabled per SIP user (the "Record Calls" checkbox). Files are stored in /var/spool/asterisk/monitor.
Summary by Day
Aggregated CDR data per day: total calls, total minutes, total cost. Useful for daily revenue tracking.
Summary by Month
Same as Summary by Day but grouped per month. Use for monthly billing and revenue reports.
Summary by User
Per-user usage and cost breakdown for the selected period.
Summary by Trunk
Per-trunk usage and cost. Helps identify which providers handle the most traffic and revenue.
DID History
Inbound call history for DID numbers. Shows which DID was called, who answered, and what happened.
Offer CDR
CDRs filtered to calls covered by an active Offer (unlimited country package). These calls have cost=0 because they fall under the user\'s offer.
Offer Use
Offer activity report. For each user with an active offer, shows progress bar (days remaining), total calls made, and total minutes consumed.
Tariffs (Rates)
Pricing per destination prefix. Each rate has:
| Field | Description |
|---|---|
| Prefix | Destination prefix (e.g., 1 for USA, 44 for UK) |
| Destination | Human-readable name (e.g., "USA Mobile") |
| Rate | Price per minute (sell rate) |
| Initial Block | Minimum billed seconds (e.g., 60 = first minute charged in full) |
| Billing Block | Granularity after initial block (e.g., 1 = per second, 60 = per minute) |
| Connect Charge | Flat fee added per call |
| Trunk | Optional — force this rate to use a specific trunk (LCR override) |
905551234567 and you have prefixes 9, 90, and 905, the rate with prefix 905 is selected.Plans
A pricing plan groups one or more rate decks together. Users are assigned a plan, which determines what rates they pay.
You can have one plan with all rates, or different plans for different markets (e.g., "USA Plan", "Europe Plan").
Rate Decks
A rate deck is a collection of rates (typically imported from a CSV from your wholesale provider). Plans reference rate decks.
Import CSV
Upload a CSV with columns: prefix,destination,rate,initblock,billingblock,connectcharge. The system auto-creates rate entries.
Prefixes
Reference table of country/destination prefixes (e.g., 1=USA, 44=UK). Used in dropdowns when creating rates so you don\'t need to remember prefixes manually.
User Custom Rates
Per-user prefix override above the user\'s plan rates. Use cases:
- Give one specific customer a discount on a specific country
- Charge premium for a specific user
Match priority: User Custom Rate > Plan Rate > Default Rate Deck.
Offers
Unlimited-country packages: a user can call a specific prefix for free for N days. Sold as add-on packages.
Fields
| Field | Description |
|---|---|
| User | The user the offer applies to |
| Prefix | Destination prefix covered (e.g., 1 for USA) |
| Destination | Display name (e.g., "USA Unlimited") |
| Days | Validity period in days from start |
| Starts at / Expires at | Auto-calculated dates |
While active, calls matching the offer prefix are billed at cost=0 and tracked in CDR with offer_id.
Reseller Margin
Defines the markup % a reseller adds on top of the wholesale rate. Reseller\'s clients see the marked-up rate.
Example: wholesale rate $0.01/min, reseller margin 50% → reseller\'s clients pay $0.015/min. The 50% difference is reseller revenue.
Trunks
Upstream SIP providers used to terminate outbound calls (and optionally receive inbound).
Fields
| Field | Description |
|---|---|
| Name | Internal identifier (used in dialplan as trunk-{name}) |
| Host / Port | Provider SIP server |
| Username / Password | If provider requires Digest auth (REGISTER mode) |
| Codecs | Allowed codecs |
| Max Channels | Concurrent call cap |
| Trunk Prefix (Add) | Prepend to dialed number before sending to provider |
| Strip Prefix (Remove) | Remove from dialed number before sending |
| Register | Toggle outbound REGISTER (provider authentication) |
| Status | active / inactive |
| Priority | Lower number = higher preference (used in trunk groups) |
Two authentication modes
- IP-to-IP: Provider whitelists your server IP. No username/password needed. Leave register off.
- Username + Password (REGISTER): Toggle "Register" on; system sends REGISTER with Digest auth.
Register Status badge
Trunks list shows a "Register" column with OK (green) if registered, NO (red) if registration failed. Updated via AJAX every page load.
Trunk Groups
Group multiple trunks together for failover. If the first trunk responds with CONGESTION or CHANUNAVAIL, the dialplan automatically tries the next trunk in the group.
Order matters — set priority on each trunk_group_trunk row to control failover sequence.
Providers
Trunks can be optionally tagged with a provider name (your wholesale company). Used for reporting and grouping.
Trunk Prefix Manipulation
Some providers require number reformatting before they accept the call:
- Strip Prefix: If number starts with this string, remove it. Example: strip
+90from+905551234567→5551234567. - Add Prefix: Prepend to result. Example: add
00→005551234567.
Both can be set together; strip is applied first, then add.
For multi-trunk routing (failover), each trunk applies its own prefix manipulation.
Trunk Register
When "Register" is enabled, Asterisk sends a SIP REGISTER request to the provider with the configured username/password. Once accepted, the provider can route inbound calls to your server.
Status appears in the Trunks list as a colored badge.
Troubleshooting
- Check provider gave you correct credentials
- Check firewall allows outbound SIP (UDP 5060) and inbound responses
- Check Asterisk full log:
tail -f /var/log/asterisk/full | grep REGISTER
Payments
Manual payment records — when you receive money from a customer, log it here. Adds credit to the user.
Webhook integrations (cgw, heleket) auto-create payment records for crypto payments.
Vouchers
Prepaid voucher codes. Generate codes worth $X each, distribute to customers, they redeem to top up balance.
Invoices
Generate invoices for customers — useful for postpaid billing or accounting records.
Services
Recurring monthly services that customers can subscribe to (e.g., DID rental, premium support). Auto-billed monthly.
Web Phone
RESELLER CLIENT
Browser-based softphone using WebRTC (Janus SIP plugin). No software install needed.
How it works
- Open Dialer → Web Phone
- If you have multiple SIP users, select one
- System auto-registers with Asterisk via Janus
- Status shows "Registered - {username}"
- Type number, click Call
Features
- Inbound call popup with answer/decline
- DTMF dialpad during call
- Call duration timer
- Hangup, hold, transfer
- Connected line update — when bridged to another party, the displayed number updates
DTMF Capture
Captures DTMF digits pressed by the remote party during in-call. Useful for monitoring IVR responses or recording PIN entries.
Click Calls (Click-to-Call Widget)
CLIENT
Embed a JavaScript snippet on any website. Visitors click a floating button, enter their phone number, and your operator\'s web phone rings instantly. Once answered, the visitor is automatically called and bridged.
Setup
- Sidebar: Dialer → Click Calls → "+ New Widget"
- Choose name, your SIP user (the operator who will answer), Caller ID, working hours, button color/text
- Save → Embed Code page shows the snippet
- Copy the script tag
- Paste into your website before
</body> - Floating button appears on every page of your site
Visitor flow
- Visitor clicks the floating button
- Popup opens — visitor enters phone and optional message
- Visitor clicks "Call Me"
- Your SIP user\'s web phone rings (caller ID = visitor\'s number)
- You answer → Asterisk bridges visitor (calls visitor\'s phone via your trunk)
- Both phones connected → conversation
Working hours
If a visitor clicks outside business hours, the request is queued as a callback. The system automatically calls them when business resumes.
Logs
Each widget has its own logs page showing all visitor requests, status (calling / answered / missed / callback queued), and timestamps.
widget.callerid if set, otherwise sip_user.callerid. The visitor sees this number on their phone, not their own.DIDs
Direct Inward Dial numbers — your inbound phone numbers. When someone calls your DID, the call is routed according to the DID Destination configuration.
DID Destinations
Defines what happens when a DID is called. Options:
- SIP User: Ring a specific SIP user
- IVR: Play an IVR menu
- Queue: Place caller in a queue
- External: Forward to an outside number (uses a trunk)
- Voicemail: Send to voicemail
You can have time-of-day routing: different destinations during business hours vs. after hours.
IVR Builder
Drag-and-drop visual editor for building IVR menus. Each node is an action (play audio, get input, transfer, hang up, etc.) with branching based on caller input.
Node types
- Play audio
- Get DTMF input
- Transfer to extension/queue
- Conditional branch
- Hangup
Save IVR and assign it to a DID Destination.
Queues
Asterisk queues for distributing calls to multiple agents. Configure ringtone, hold music, max wait, agent strategy (ringall, leastrecent, etc.).
Queue Members
Add SIP users to queues. Members can dynamically pause/unpause from the Queue Dashboard.
Holidays
Define holidays. DID Destinations can route differently on holidays (e.g., always to voicemail).
Audio Files
Upload audio files (.wav, .mp3) for IVR prompts, hold music, voicemail greetings. Stored in /var/lib/asterisk/sounds/cellhub.
Auto-converted to Asterisk-compatible format on upload.
Configuration
Global system settings:
- Site name, default currency, timezone
- Default low credit threshold (alert level)
- Tax rate
- Brand colors / logo
Group Users
Group accounts together for shared billing or quotas. Useful for departments or enterprise accounts.
Resellers
Manage reseller accounts — wholesale customers who resell your service to their own clients with markup.
SMTP
Configure outbound email server. Required for password reset, low credit alerts, payment notifications.
Test "Send Test Email" button to verify before going live.
Mail Templates
Edit email templates: welcome email, invoice notification, low credit alert, password reset, etc. Supports variables like @{{user_name}}, @{{credit}}.
System Status
Live health check: Asterisk, Janus, MySQL, queue worker. Each shows a green/red indicator.
API
API key management. Generate keys for external integrations (CRM, IVR scripts, etc.). Each key can be scoped to specific endpoints.
See API Documentation page for endpoint reference.
System Updates
One-click upgrade to the latest CellHub Billing release.
What happens during update
- License + IP verification with cellhubbilling.org
- Database backup (
/var/backups/cellhub/db_pre_*.sql.gz) - Code backup (
/var/backups/cellhub/code_pre_*.tar.gz) - Bundle download with SHA256 verification
- Code rsync (preserves
.env, logs, uploads) - composer install + database migration
- AGI sync + Asterisk reload + PHP-FPM reload
- Update version + cleanup
If a step fails, the previous version is preserved (rollback by restoring backup).
Billing Engine (AGI Architecture)
The billing logic lives in /var/lib/asterisk/agi-bin/cellhub_billing.php. It runs on every outbound call.
Two modes
- check — Runs at call start. Validates credit, finds rate, selects trunk, sets channel variables. Aborts call if no funds, no trunk, or restricted.
- hangup — Runs at call end. Calculates cost from billsec × rate, deducts from balance, inserts CDR record.
Rate selection priority
- Active Offer matching destination prefix → cost = 0
- User Custom Rate matching prefix
- Plan rate matching prefix (longest prefix wins)
- Default rate deck fallback
- If none → CONGESTION (call aborted)
Dialplan & Sequential Failover
Outbound calls enter the [billing] context in /etc/asterisk/extensions.conf. Flow:
1. AGI(cellhub_billing.php,check) — validates + sets CELLHUB_TRUNK_CSV
2. trytrunk loop:
- Pick trunk N from CSV
- Apply per-trunk number prefix
- Apply CALLERID override (if set)
- Dial(PJSIP/{number}@trunk-X, 60, gTtoU(send_connected,s,1))
- On CONGESTION/CHANUNAVAIL → next trunk
- Otherwise → done
3. h extension: AGI(cellhub_billing.php,hangup) — finalizes CDR + balance
Trunk Selection Priority
| # | Source | When |
|---|---|---|
| 1 | SIP user → trunk_group_id | Always wins if SIP user has a trunk group |
| 2 | SIP user → trunk_id (single) | If SIP user has only single trunk override |
| 3 | Rate → trunk_id | The matching tariff specified a trunk (LCR) |
| 4 | Default (first active trunk) | If nothing else matched |
The SIP-user-level assignment always overrides rate-level and default.
Troubleshooting
SIP user can\'t register
- Check password matches (DB sip_users.password vs softphone)
- Check Asterisk full log:
tail -f /var/log/asterisk/full | grep "Registration" - Verify SIP transport is enabled:
asterisk -rx "pjsip show transports" - Check firewall allows UDP 5060 (or TCP/WSS as needed)
Call fails immediately
- Check user has credit:
SELECT credit FROM users WHERE id = X - Check matching rate exists for destination
- Check trunk is registered/reachable
- Asterisk console:
asterisk -rvvvwhile making the call
Web Phone won\'t register
- Browser console: any WebRTC errors?
- Check Janus is running:
systemctl status janus - Check Janus reachable through nginx:
curl https://yourdomain/janus - Verify HTTPS (WebRTC requires secure context)
Click Calls operator phone rings but visitor isn\'t called
- Check SIP user is online (web phone open)
- Check user credit and trunk availability
- View click_to_call_logs for error message
Rate doesn\'t match destination
- Verify prefix exists in rate deck assigned to user\'s plan
- Check longest prefix matching: a rate with prefix
1wins over11only if no11exists