Wholesale Complete Example

This section walks through a complete example of building a Wholesale voice transit platform from scratch.

Scenario Description

Company: A voice carrier providing domestic/international voice termination to 3 downstream customers (tenants).

Upstream Carriers:

  • Carrier A: Domestic routes, SIP trunk 10.0.1.100:5060
  • Carrier B: International routes, SIP trunk 10.0.2.50:5060

Downstream Customers:

  • Customer X: 50K calls/day, primarily domestic
  • Customer Y: 20K calls/day, domestic + international
  • Customer Z: 10K calls/day, international only

Step 1: Database Preparation

# MySQL
mysql -u root -p <<EOF
CREATE DATABASE rustpbx CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
CREATE USER 'rustpbx'@'%' IDENTIFIED BY 'StrongP@ssw0rd';
GRANT ALL PRIVILEGES ON rustpbx.* TO 'rustpbx'@'%';
FLUSH PRIVILEGES;
EOF

Step 2: config.toml

http_addr = "0.0.0.0:8080"
database_url = "mysql://rustpbx:StrongP@ssw0rd@10.0.0.100:3306/rustpbx"
external_ip = "203.0.113.10"

[proxy]
addr = "0.0.0.0"
udp_port = 5060
modules = ["acl", "auth", "registrar", "call"]
media_proxy = "auto"
addons = ["wholesale"]

[proxy.wholesale]
route_cache_capacity = 10000
route_cache_ttl_secs = 30

[proxy.wholesale.circuit_breaker]
failure_threshold = 5
open_duration_secs = 30
half_open_probes = 1
failure_codes = [503, 408, 504]

[console]
session_secret = "my-console-secret-2026"

Step 3: Configure SIP Trunks

mkdir -p config/trunks

config/trunks/carrier_a.toml (domestic carrier):

[[trunk]]
name = "carrier-a"
dest = "sip:10.0.1.100:5060"
backup_dest = "sip:10.0.1.101:5060"
direction = "outbound"
codec = ["pcmu", "pcma", "g729"]
max_calls = 300
max_cps = 50
weight = 100

[trunk.auth]
username = "myacct_a"
password = "pass_a"

config/trunks/carrier_b.toml (international carrier):

[[trunk]]
name = "carrier-b"
dest = "sip:10.0.2.50:5060"
direction = "outbound"
codec = ["pcmu", "pcma", "g729", "g722"]
max_calls = 200
max_cps = 30
weight = 100

[trunk.auth]
username = "myacct_b"
password = "pass_b"

Step 4: Start the Service

docker run -d --name rustpbx \
  -v $(pwd)/config.toml:/app/config.toml \
  -v $(pwd)/config:/app/config \
  -p 8080:8080 -p 5060:5060/udp \
  docker.cnb.cool/miuda.ai/rustpbx:latest

Step 5: Web Console Configuration

Access http://203.0.113.10:8080, create a superuser, and enter the console.

5.1 Create Rate Decks

Wholesale → Rate Decks → Create New:

NameType
buy-carrier-aBuy
buy-carrier-bBuy
sell-domesticSell
sell-internationalSell

Add rate entries:

buy-carrier-a (buy-domestic):

PrefixRateMin DurationIncrement
86100.02566
86210.02566
860.03566

buy-carrier-b (buy-international):

PrefixRateMin DurationIncrement
10.0666
440.0566
810.0766

sell-domestic (sell-domestic):

PrefixRateMin DurationIncrement
86100.0466
86210.0466
860.0566

sell-international (sell-international):

PrefixRateMin DurationIncrement
10.1066
440.0866
810.1266

5.2 Configure Trunk Wholesale Parameters

Wholesale → Trunk Management:

  • carrier-a → Buy rate deck: buy-carrier-a, enable circuit breaker
  • carrier-b → Buy rate deck: buy-carrier-b, enable circuit breaker

5.3 Create Routing Profiles

Wholesale → Routing Profiles → Create New:

Profile “profile-domestic” (domestic routing):

PriorityTrunkPrefixWeight
10carrier-a86100

Profile “profile-full” (domestic + international):

PriorityTrunkPrefixWeight
10carrier-a86100
10carrier-b1100
10carrier-b44100
10carrier-b81100

Profile “profile-international” (international only):

PriorityTrunkPrefixWeight
10carrier-b1100
10carrier-b44100
10carrier-b81100

5.4 Create Tenants

FieldCustomer XCustomer YCustomer Z
NameCustomerX-DomesticCustomerY-FullCustomerZ-International
Balance500003000020000
Credit Limit500030002000
Sell Rate Decksell-domesticsell-domestic + sell-internationalsell-international
Routing Profileprofile-domesticprofile-fullprofile-international
Max Concurrent20010050
Max CPS301510
LCREnabledEnabledEnabled

5.5 Bind Tenant Trunks

Add inbound trunk bindings for each tenant, configuring source IPs:

  • Customer X: Source IP 198.51.100.10
  • Customer Y: Source IP 198.51.100.20
  • Customer Z: Source IP 198.51.100.30

5.6 Hot Reload

Click the “Reload” button on rate decks and routing profiles.

Step 6: Verification

6.1 Test Call

From Customer X’s softswitch, place a call to 861012345678:

  • Should match routing prefix 8610 (priority 10)
  • Select carrier-a
  • Match sell rate 8610 = 0.04 per minute
  • Match buy rate 8610 = 0.025 per minute

6.2 Check CDR

Wholesale → CDR Management: Confirm CDR has been generated with correct fields.

6.3 Check Balance

Tenant Details: Confirm balance has been deducted.

6.4 Price Calculator Verification

Tenant Details → Price Calculator:

  • Enter 861012345678 → shows prefix 8610, sell 0.04, buy 0.025, profit 0.015
  • Enter 12125551234 → Customer X should show “no route” (domestic profile only)

Step 7: Daily Operations

OperationFrequencyLocation
Review monitoring dashboardDailyWholesale console
Check circuit breaker statusDailyMonitoring & Throttling
Verify balancesDailyTenant Details
Export CDRsWeeklyCDR Management → Export
Generate invoicesMonthlyBilling Management
Rate adjustmentsAs neededRate Decks → Reload
Database backupDailyMySQLdump / pg_dump