29 KiB
CPaaS WebSocket Media Relay — Architecture & Integration Plan
Version: 1.0
Status: Design
Scope: Inbound + outbound voice media relay for Bonvoice (and future providers)
Primary goal: Act as a transparent WebSocket tunnel between the telecom provider and the customer AI backend, with authentication and routing control at the relay layer and no added media processing latency.
1. Executive Summary
Your CPaaS sits between:
| Leg | Party | Protocol |
|---|---|---|
| Provider leg | Bonvoice (or other CPaaS) | WebSocket + JSON media frames |
| Customer leg | Tenant AI / voicebot backend | WebSocket + same JSON frame format |
The relay must not decode, transcode, re-chunk, or buffer audio in the hot path. It forwards WebSocket text frames as-is after authentication and tenant routing.
You own:
- Provider credentials (Bonvoice token, DID inventory)
- Customer API keys / JWT issuance
- Session routing (
call_id,stream_id, DID → tenant) - Audit logs, rate limits, billing metadata
- Outbound call orchestration API
Customers own:
- AI logic, STT/TTS, conversation state
- Their backend WebSocket endpoint (or pre-connect to your upstream socket)
2. Design Principles
2.1 Zero-processing media path
Bonvoice WS ──► Relay (auth + route) ──► Customer WS
▲ │ │
└────────────────────┴──────────────────────┘
raw JSON text frames, both directions
| Do | Don't |
|---|---|
| Forward raw WebSocket text frames | json.loads() on every media event |
Parse only control events (start, connected, end) |
Base64 decode / PCM transcoding |
Use async bidirectional copy (asyncio.gather) |
Artificial sleep() or batching |
| Colocate relay in same region as provider | Cross-region relay for real-time audio |
| Pre-resolve tenant route before bridging | DB lookups inside media loop |
Target added latency: < 5 ms p99 per hop (relay processing only, excluding network RTT to customer).
2.2 Fail closed on auth
If authentication or routing fails, reject before bridging. Never forward media to an unauthenticated or unmapped session.
2.3 Provider URL is always yours
Bonvoice (inbound template + outbound API) is configured with your wss:// endpoint. Customers never expose their URL to the provider directly. This gives you full control over access, metering, and multi-tenancy.
3. System Context
flowchart LR
subgraph Provider["Telecom Provider (Bonvoice)"]
DID[Virtual DID]
PBX[PBX / Voicebot Platform]
end
subgraph Relay["Your CPaaS Relay"]
GW[WebSocket Gateway]
AUTH[Auth & Routing]
API[REST Control API]
SESS[Session Store]
end
subgraph Customer["Customer Tenant"]
BOT[AI Voicebot Backend]
end
Caller((Caller)) --> DID
DID --> PBX
PBX <-->|wss JSON media| GW
GW <--> AUTH
AUTH <--> SESS
API --> SESS
Customer -->|API key| API
GW <-->|wss JSON media| BOT
4. Bonvoice Provider Protocol Reference
Based on validated integration behaviour.
4.1 Connection
- Transport: WebSocket (
wss://), not HTTP POST - Bonvoice server IP observed:
103.131.25.5(maintain allowlist) - Endpoint must use
wss://, nothttps://
4.2 Message types
Call start
{
"event": "start",
"data": {
"stream_id": "<id>",
"call_id": "<id>",
"from": "<caller>",
"to": "<did>"
}
}
Media (bidirectional)
{
"event": "media",
"stream_id": "<id>",
"media": {
"packet_id": 1,
"timestamp": 0,
"payload": "<base64 PCM>"
}
}
- Audio: PCM 16-bit, 8000 Hz, mono
- Chunk size: 320 bytes (20 ms), multiples of 320 only
Interruption (customer → provider)
{
"event": "clear",
"stream_id": "<id>"
}
Additional events observed
| Event | Direction | Relay action |
|---|---|---|
connected |
Provider → relay | Forward as-is; optional metadata log |
end |
Provider → relay | Forward, then close both legs gracefully |
4.3 Outbound API (control plane)
POST https://backend.pbx.bonvoice.com/autoDialManagement/autoCallBridging/
Authorization: Token <provider_token>
{
"autocallType": "5",
"destination": "<callee>",
"legACallerID": "<did>",
"eventID": "<unique_id>",
"voicebotProvider": "BONVOICE",
"voicebotURL": "wss://<your-relay-host>/v1/media/<session_token>"
}
Response:
{
"responseCode": 200,
"responseDescription": "Success",
"responseType": "Success",
"eventID": "..."
}
5. Relay Component Architecture
flowchart TB
subgraph ControlPlane["Control Plane"]
REST[REST API]
PROV[Provider Adapter - Bonvoice]
TENANT[Tenant Registry]
KEYS[API Key / JWT Service]
end
subgraph DataPlane["Data Plane - hot path"]
PWS[Provider WebSocket Handler]
TUNNEL[Frame Tunnel]
CWS[Customer WebSocket Client/Handler]
end
REST --> PROV
REST --> KEYS
REST --> TENANT
PWS --> TUNNEL
TUNNEL --> CWS
TENANT --> PWS
KEYS --> PWS
KEYS --> CWS
| Component | Responsibility |
|---|---|
| Provider WebSocket Handler | Accept Bonvoice WS; provider-side auth |
| Customer WebSocket Client | Dial customer upstream WS (outbound leg) |
| Frame Tunnel | Bidirectional raw frame copy |
| Session Store | Redis: session_token → tenant, upstream URL, metadata |
| Tenant Registry | DID → tenant; tenant → upstream WS URL + credentials |
| Provider Adapter | Bonvoice outbound API, token rotation |
| REST API | Customer-facing call control + config |
6. WebSocket Tunnel — Core Mechanism
6.1 Session lifecycle
1. Provider connects → wss://relay/v1/media/{session_token}
2. Relay validates session_token (or resolves inbound by DID on `start`)
3. Relay opens upstream → wss://customer/configured-endpoint
4. Relay starts two async copy loops
5. On `end` or disconnect → cancel both loops, purge session
6.2 Tunnel pseudocode (hot path)
async def tunnel(provider_ws, customer_ws):
async def p2c():
async for raw in provider_ws:
await customer_ws.send(raw) # no parse, no modify
async def c2p():
async def c2p():
async for raw in customer_ws:
await provider_ws.send(raw)
await asyncio.gather(p2c(), c2p())
6.3 Optional control-plane hook (non-blocking)
Parse only the first start frame (and end) in a side task for logging/billing. Never block the copy loop.
async def observe_control_events(raw: str, session: Session):
if '"event":"start"' in raw: # or parse once with orjson
session.mark_started()
elif '"event":"end"' in raw:
session.mark_ended()
Use string prefix check or single parse on non-media events only.
6.4 Upstream connection modes
| Mode | Latency | Use case |
|---|---|---|
A. Relay dials customer on start |
+1 WS handshake (~30–150 ms) | Simplest; acceptable for most bots |
| B. Customer pre-connects to relay | Lowest | Customer opens wss://relay/v1/upstream/{session_token} before answering |
| C. Persistent tenant socket | Low | Customer keeps one WS open; relay multiplexes by stream_id (complex) |
Recommendation: Start with Mode A; offer Mode B for latency-sensitive tenants.
7. Authentication Model
Authentication is enforced at three layers.
7.1 Layer 1 — Provider leg (Bonvoice → Relay)
| Method | Implementation |
|---|---|
| IP allowlist | Only accept WS from known Bonvoice egress IPs |
| Session token in URL | Outbound: wss://relay/v1/media/{session_token} — token is single-use, TTL 5 min |
| Inbound DID routing | On start, map to (DID) → tenant; reject unknown DID |
| mTLS (optional) | Mutual TLS if Bonvoice supports client certs |
Provider token (Authorization: Token ...) is used only on outbound REST API calls from relay to Bonvoice, never exposed to customers.
7.2 Layer 2 — Relay control plane (Customer → Relay REST)
| Method | Implementation |
|---|---|
| API key | Authorization: Bearer sk_live_... on all REST endpoints |
| Scoped permissions | calls:write, numbers:read, etc. |
| Rate limiting | Per-tenant token bucket |
7.3 Layer 3 — Customer leg (Relay → Customer backend)
Choose one per tenant (configured in dashboard):
| Method | Header / mechanism |
|---|---|
| Static secret | Authorization: Bearer <tenant_upstream_secret> |
| Signed JWT | Short-lived JWT signed by relay, claims: tenant_id, session_id, call_id |
| HMAC query param | wss://customer/bot?sig=HMAC(session_token, secret) |
| mTLS | Client cert per tenant |
Customer validates the relay; relay validates the customer using pre-shared secret or JWT.
7.4 Session token design
session_token = base64url(tenant_id || call_uuid || HMAC-SHA256(...))
Properties:
- Single-use or idempotent per
eventID - TTL: 300 seconds
- Bound to: tenant, direction, destination/caller, DID
- Stored in Redis before outbound API call
8. Inbound Call Flow
sequenceDiagram
participant Caller
participant Bonvoice
participant Relay
participant Redis
participant Customer
Caller->>Bonvoice: Inbound call to DID
Bonvoice->>Relay: wss connect /v1/media/inbound
Bonvoice->>Relay: event=start {to: DID, from, call_id, stream_id}
Relay->>Redis: Lookup DID → tenant + upstream URL
alt Unknown DID or tenant suspended
Relay-->>Bonvoice: Close WS (4403)
else OK
Relay->>Customer: wss connect + auth headers
Relay->>Relay: Start bidirectional tunnel
loop Every 20ms
Bonvoice->>Relay: event=media
Relay->>Customer: forward raw frame
Customer->>Relay: event=media
Relay->>Bonvoice: forward raw frame
end
Bonvoice->>Relay: event=end
Relay->>Customer: forward end
Relay->>Relay: Close both sockets
end
8.1 Bonvoice configuration (per DID)
| Field | Value |
|---|---|
| Provider | BONVOICE |
| Endpoint | wss://media.yourcpaas.com/v1/media/inbound |
All tenants share one inbound WS URL; routing happens on start.data.to (DID).
8.2 Inbound routing table
DID 7946350706 → tenant_abc → wss://bot.customer.com/voice
DID 7946350707 → tenant_xyz → wss://api.other.com/ws/bonvoice
9. Outbound Call Flow
sequenceDiagram
participant Customer
participant Relay
participant Redis
participant Bonvoice
participant Callee
Customer->>Relay: POST /v1/calls {to, from_did} + API key
Relay->>Relay: Validate API key, quota
Relay->>Redis: Store session_token → tenant, upstream, metadata
Relay->>Bonvoice: POST autoCallBridging {voicebotURL: wss://relay/.../session_token}
Bonvoice-->>Relay: 200 Success
Relay-->>Customer: {call_id, session_id, status: initiated}
opt Mode B - pre-connect
Customer->>Relay: wss /v1/upstream/{session_token}
end
Bonvoice->>Callee: Ring
Callee->>Bonvoice: Answer
Bonvoice->>Relay: wss /v1/media/{session_token}
Relay->>Redis: Validate session_token
Relay->>Customer: wss connect upstream (Mode A) or attach pre-connect (Mode B)
Relay->>Relay: Tunnel media both ways
9.1 Customer REST API (your CPaaS)
POST /v1/calls
Authorization: Bearer sk_live_...
Content-Type: application/json
{
"to": "9149154337",
"from": "7946350706",
"upstream": "wss://bot.customer.com/voice",
"metadata": { "campaign_id": "xyz" }
}
Response:
{
"id": "call_01J...",
"session_token": "...",
"status": "initiated",
"provider_event_id": "11A31A12FF1C4455"
}
Relay internally calls Bonvoice with voicebotURL = wss://media.yourcpaas.com/v1/media/{session_token}.
10. URL & Routing Scheme
| Endpoint | Purpose | Auth |
|---|---|---|
wss://media.../v1/media/inbound |
All inbound Bonvoice streams | IP allowlist + DID routing on start |
wss://media.../v1/media/{session_token} |
Outbound + optional direct sessions | Session token validation |
wss://media.../v1/upstream/{session_token} |
Customer pre-connect (Mode B) | API key or session_token |
POST /v1/calls |
Initiate outbound | Customer API key |
GET /v1/calls/{id} |
Call status | Customer API key |
POST /v1/numbers |
Assign DID to tenant | Customer API key |
11. Multi-Tenancy & Data Model
11.1 Core entities
Tenant
- id, name, status
- api_key_hash
- upstream_ws_url
- upstream_auth_type, upstream_secret
- quota_limits
PhoneNumber (DID)
- e164, tenant_id, provider, provider_account_id
CallSession
- session_token, tenant_id, direction
- call_id, stream_id, from, to
- status: pending | bridging | active | ended | failed
- created_at, answered_at, ended_at
ProviderAccount
- provider: bonvoice
- token_encrypted, default_caller_id
11.2 Redis session record (ephemeral)
{
"session_token": "abc123",
"tenant_id": "ten_01",
"direction": "outbound",
"upstream_ws": "wss://bot.customer.com/voice",
"upstream_auth": "Bearer xxx",
"to": "9149154337",
"from": "7946350706",
"status": "pending",
"expires_at": 1699999999
}
TTL matches Bonvoice ring timeout + margin (e.g. 10 minutes).
12. Latency Budget
| Stage | Target |
|---|---|
| Relay frame forward (provider → customer) | < 2 ms p99 |
| Relay frame forward (customer → provider) | < 2 ms p99 |
| Session lookup (Redis) | < 1 ms |
| Upstream WS connect (Mode A, one-time) | 30–150 ms (not per frame) |
| JSON parse on hot path | 0 (avoided) |
| Audio transcoding | 0 (avoided) |
20 ms audio frames: relay must sustain > 50 frames/sec per call with headroom for 500+ concurrent calls per node.
13. Failure Handling
| Scenario | Behaviour |
|---|---|
| Invalid session token | Close provider WS immediately with code 4401 |
| Unknown inbound DID | Close after start with 4403; log alert |
| Customer upstream unreachable | Retry connect 2× with 100 ms backoff; then close provider leg |
| Customer WS drops mid-call | Close provider leg; emit webhook call.upstream_disconnected |
| Provider WS drops | Close customer leg |
| Bonvoice sends POST not WS | Return 426 + log; misconfigured URL |
| Provider API failure on outbound | Return error to customer; do not create session |
13.1 Webhook events (control plane, async)
Deliver to customer via HTTP POST (not in media path):
call.initiatedcall.answeredcall.media_startedcall.endedcall.failed
14. Security Checklist
- Provider token stored encrypted (KMS / vault); never in logs
- Customer API keys stored as hashes
- Session tokens: cryptographically random, single-use, short TTL
- TLS 1.2+ on all WS and REST
- IP allowlist for Bonvoice egress
- Per-tenant rate limits on
/v1/calls - No customer can specify arbitrary
voicebotURLto Bonvoice — relay always injects its own URL - Audit log: who initiated call, DID used, duration, bytes relayed
15. Observability
15.1 Metrics (per call)
relay_session_active{tenant, direction}relay_frame_forward_total{direction=provider_to_customer|customer_to_provider}relay_frame_forward_latency_ms(histogram)relay_upstream_connect_duration_msrelay_auth_failure_total{reason}
15.2 Tracing
- Trace ID =
call_idorsession_token - Span:
provider.accept→session.resolve→upstream.connect→tunnel.active
15.3 Logging
Log control events only (start, end, connected). Do not log media payloads.
16. Implementation Roadmap
Phase 1 — MVP tunnel (2–3 weeks)
- WebSocket gateway with raw frame tunnel
- Redis session store
- Outbound:
POST /v1/calls→ Bonvoice API → session routing - Inbound: DID → tenant lookup on
start - API key auth on REST
- Provider IP allowlist
- Basic metrics + structured logs
Phase 2 — Production hardening (2 weeks)
- Customer upstream auth (JWT / HMAC)
- Mode B pre-connect upstream endpoint
- Webhooks for call lifecycle
- Graceful shutdown / connection draining
- Horizontal scaling behind L4 LB with sticky sessions OR shared Redis session state
Phase 3 — Platform (ongoing)
- Multi-provider adapter interface (abstract Bonvoice)
- Dashboard: DID management, API keys, call logs
- Billing meter: call duration + minute rounding
- HA Redis cluster, multi-region consideration
17. Deployment Topology
flowchart TB
subgraph Internet
BV[Bonvoice]
CUST[Customer Backends]
end
subgraph YourCloud["Your CPaaS Region (e.g. ap-south-1)"]
LB[L4/L7 Load Balancer]
R1[Relay Node 1]
R2[Relay Node 2]
REDIS[(Redis Cluster)]
API[Control API]
DB[(PostgreSQL)]
end
BV -->|wss| LB
LB --> R1
LB --> R2
R1 --> REDIS
R2 --> REDIS
API --> DB
API --> REDIS
R1 -->|wss| CUST
R2 -->|wss| CUST
CUST -->|REST| API
Notes:
- WebSocket LB must support connection upgrade and long-lived connections
- Sticky sessions optional if session state is fully in Redis
- Run relay nodes close to Bonvoice infrastructure (India) to minimize RTT
18. Provider Abstraction (future-proof)
Define a provider interface so Bonvoice is one adapter:
class VoiceProvider(Protocol):
async def initiate_outbound(self, req: OutboundCallRequest) -> ProviderCallRef: ...
def media_websocket_path(self) -> str: ...
def validate_inbound(self, start_event: dict) -> bool: ...
Bonvoice adapter implements autoCallBridging and protocol quirks (connected, end events).
19. Customer Integration Guide (summary for docs)
19.1 Inbound
- Customer claims a DID via your dashboard/API
- Customer registers upstream WebSocket URL
- You configure Bonvoice template →
wss://media.yourcpaas.com/v1/media/inbound - Customer implements Bonvoice JSON protocol on their WS server
- Customer receives
start, thenmediaframes; responds withmediaand optionalclear
19.2 Outbound
- Customer calls
POST /v1/callswith API key - Optional: pre-connect to
wss://media.yourcpaas.com/v1/upstream/{session_token} - Relay bridges Bonvoice ↔ customer when callee answers
- Customer handles protocol identically to direct Bonvoice integration
19.3 What customers do NOT need
- Bonvoice account or token
- Direct exposure of their WS URL to Bonvoice
- Provider-specific outbound API knowledge
20. Risks & Mitigations
| Risk | Mitigation |
|---|---|
| Added latency from relay hop | Raw tunnel, no parsing, regional colocation |
| Upstream connect delay (Mode A) | Offer pre-connect Mode B |
| Single relay node failure | Multiple nodes + LB + Redis |
| Token leakage in URL | Short TTL, single-use, HTTPS/WSS only |
| Tenant sends malformed media | Provider enforces 320-byte rule; relay forwards as-is |
| Bonvoice protocol changes | Provider adapter layer + contract tests |
21. Success Criteria
| Metric | Target |
|---|---|
| Relay processing latency | < 5 ms p99 per frame |
| Call setup (outbound, excl. ring time) | < 200 ms from answer to first media frame forwarded |
| Auth failure rate (invalid sessions) | 100% blocked, 0% bridged |
| Concurrent calls per node | ≥ 500 with < 60% CPU |
| Uptime | 99.9% for media gateway |
22. Appendix — Mapping from Current PoC
Your existing PoC maps to the relay as follows:
| PoC file | Relay equivalent |
|---|---|
main.py (handle_media_stream) |
Replace playback logic with tunnel(provider_ws, customer_ws) |
outbound_call.py |
Becomes internal BonvoiceProvider.initiate_outbound() |
audio_utils.py |
Not used in relay — only in test/demo tooling |
| Bonvoice template endpoint | wss://<relay>/v1/media/inbound |
voicebotURL in outbound API |
wss://<relay>/v1/media/{session_token} |
23. Appendix — Bonvoice Credentials & API Reference (PoC / Test)
Security warning: This section contains live credentials used during PoC testing.
Do not commit this file to a public repository. Rotate the provider token before production.
Store secrets in a vault (e.g. AWS Secrets Manager, HashiCorp Vault) in the relay implementation.
23.1 Provider account credentials
| Item | Value | Usage |
|---|---|---|
| Auth Token | 8de97c5932280c84c4bbf36140a1ec7ab7ae0b23 |
Authorization: Token <token> on Bonvoice REST APIs |
| Virtual DID | 7946350706 |
Inbound number; use as legACallerID on outbound |
| Provider name | BONVOICE |
Voicebot template + voicebotProvider field |
23.2 Bonvoice API endpoints
| API | Method | URL |
|---|---|---|
| Auto Call Bridging (outbound voicebot) | POST |
https://backend.pbx.bonvoice.com/autoDialManagement/autoCallBridging/ |
| Media stream (WebSocket) | GET (Upgrade) |
Configured per template / voicebotURL — must be wss://, not https:// |
Outbound request headers:
Authorization: Token 8de97c5932280c84c4bbf36140a1ec7ab7ae0b23
Content-Type: application/json
Outbound request body (validated PoC example):
{
"autocallType": "5",
"destination": "9149154337",
"legACallerID": "7946350706",
"eventID": "11A31A12FF1C4455",
"voicebotProvider": "BONVOICE",
"voicebotURL": "wss://<your-relay-or-ngrok-host>/ws/media"
}
Successful outbound response (observed):
{
"responseCode": 200,
"responseDescription": "Success",
"responseType": "Success",
"eventID": "11A31A12FF1C4455"
}
| Field | Description |
|---|---|
autocallType |
"5" — voicebot auto-call type per Bonvoice API |
destination |
Callee E.164 (no + in PoC tests) |
legACallerID |
Caller ID shown to callee; use assigned DID |
eventID |
Unique tracking ID (UUID / random hex recommended) |
voicebotProvider |
Must be BONVOICE for Bonvoice voicebot templates |
voicebotURL |
Your relay WebSocket URL — Bonvoice connects here when call is answered |
23.3 Inbound voicebot template (Bonvoice portal)
Configure under Voicebot Templates → Create new template:
| Field | PoC value |
|---|---|
| Template Name | test (or any label) |
| Provider | BONVOICE |
| Endpoint | wss://<your-relay-or-ngrok-host>/ws/media |
Important: Use wss:// (WebSocket Secure). Using https:// causes Bonvoice to send HTTP POST instead of a WebSocket upgrade, resulting in 404 Not Found.
23.4 Network & infrastructure (observed during PoC)
| Item | Value | Notes |
|---|---|---|
| Bonvoice media server IP | 103.131.25.5 |
Source IP for inbound WebSocket connections — add to provider allowlist |
| Local dev server | http://0.0.0.0:8000 |
Uvicorn PoC |
| PoC WebSocket path | /ws/media |
Also accepts / in PoC |
| ngrok URL (ephemeral) | https://1af5-2401-4900-88ce-916c-2c02-6571-5bcd-b1eb.ngrok-free.app |
Changes every ngrok restart; WSS = same host with wss:// |
PoC WebSocket endpoint used in tests:
wss://1af5-2401-4900-88ce-916c-2c02-6571-5bcd-b1eb.ngrok-free.app/ws/media
23.5 Test phone numbers
| Number | Role |
|---|---|
7946350706 |
Assigned virtual DID (inbound destination / outbound caller ID) |
9149154337 |
Test mobile used for outbound dial + inbound caller during PoC |
9846098460 |
Example destination from Bonvoice API documentation |
23.6 Observed call / stream identifiers (PoC logs)
Successful inbound WebSocket session (for correlation testing):
| Field | Value |
|---|---|
stream_id |
6379 |
call_id |
12775 |
from |
9149154337 |
to |
7946350706 |
Additional WebSocket events observed from Bonvoice (forward in relay; do not drop):
| Event | When |
|---|---|
connected |
Immediately after WS handshake, before start |
start |
Call metadata — use for DID routing / session binding |
media |
Audio frames (320-byte PCM chunks, Base64) |
clear |
Barge-in / flush playback buffer |
end |
Call teardown |
23.7 Ready-to-run curl (outbound)
Replace voicebotURL with your current relay or ngrok WSS URL:
curl -X POST "https://backend.pbx.bonvoice.com/autoDialManagement/autoCallBridging/" \
-H "Authorization: Token 8de97c5932280c84c4bbf36140a1ec7ab7ae0b23" \
-H "Content-Type: application/json" \
-d '{
"autocallType": "5",
"destination": "9149154337",
"legACallerID": "7946350706",
"eventID": "TEST'"$(date +%s)"'",
"voicebotProvider": "BONVOICE",
"voicebotURL": "wss://YOUR-RELAY-HOST/ws/media"
}'
PoC script equivalent (auto-detects ngrok URL):
cd test_bonvoice
source .venv/bin/activate
python3 outbound_call.py 9149154337
23.8 Environment variables (recommended for relay implementation)
| Variable | PoC / example value |
|---|---|
BONVOICE_TOKEN |
8de97c5932280c84c4bbf36140a1ec7ab7ae0b23 |
BONVOICE_DID |
7946350706 |
BONVOICE_VOICEBOT_URL |
wss://media.yourcpaas.com/v1/media/inbound |
BONVOICE_API_URL |
https://backend.pbx.bonvoice.com/autoDialManagement/autoCallBridging/ |
BONVOICE_MEDIA_ALLOWLIST |
103.131.25.5 |
23.9 Audio format requirements (provider-enforced)
| Parameter | Value |
|---|---|
| Encoding | PCM |
| Bit depth | 16-bit |
| Sample rate | 8000 Hz |
| Channels | Mono |
| Chunk size | 320 bytes (multiples of 320 only) |
| Wire format | Base64 in JSON media.payload |
24. Urmi CPaaS — Implemented Customer API Routes
Urmi exposes production voice relay endpoints under /api/v1/voice (REST + WebSocket). All outbound calls validate that from is a purchased DID assigned to the authenticated account.
24.1 REST control plane
| Method | Path | Auth | Description |
|---|---|---|---|
POST |
/api/v1/voice/calls |
API key or session cookie | Initiate outbound call |
GET |
/api/v1/voice/calls/{id} |
API key or session cookie | Poll call status |
GET |
/api/v1/voice/docs |
API key or session cookie | API reference for integrators |
Create outbound call
POST /api/v1/voice/calls
Authorization: Bearer urmi_live_...
Content-Type: application/json
{
"to": "9149154337",
"from": "7946350706",
"bridge_mode": "upstream",
"upstream": "wss://bot.customer.com/voice"
}
Console testing (Urmi dashboard at /console/voice):
{
"to": "9149154337",
"from": "7946350706",
"bridge_mode": "console"
}
Validation rules:
frommust match aphone_numbersrow withstatus=assignedanduser_id= callerupstreamis required whenbridge_mode=upstream- Relay always injects Bonvoice
voicebotURL— customers never pass provider URLs
24.2 Media relay (provider leg)
| WebSocket | Purpose | Auth |
|---|---|---|
wss://<host>/api/v1/voice/ws/media |
Bonvoice outbound voicebotURL | IP allowlist + pending call match on start |
wss://<host>/api/v1/voice/media/{session_token} |
Legacy alias | Same handler |
Configure Bonvoice outbound API voicebotURL:
wss://<public-host>/api/v1/voice/ws/media
Set VOICE_RELAY_PUBLIC_URL=wss://<public-host>/api/v1/voice in production.
24.3 Console audio bridge (outbound testing)
| WebSocket | Purpose |
|---|---|
wss://<host>/api/v1/voice/console |
Browser mic/speaker bridge for bridge_mode: console |
When placing a console-bridged outbound call from /console/voice, the account must have an active console WebSocket. Provider media is forwarded to the browser; browser mic frames are relayed back to Bonvoice.
24.4 Environment variables (Urmi)
| Variable | Example |
|---|---|
REDIS_URL |
redis://redis:6379/0 |
BONVOICE_TOKEN |
8de97c5932280c84c4bbf36140a1ec7ab7ae0b23 |
BONVOICE_API_URL |
https://backend.pbx.bonvoice.com/autoDialManagement/autoCallBridging/ |
BONVOICE_MEDIA_ALLOWLIST |
103.131.25.5 |
VOICE_RELAY_PUBLIC_URL |
wss://media.yourcpaas.com/api/v1/voice |
25. Next Steps
- Approve URL scheme and auth model (Section 7, 10)
Implement Phase 1 tunnel with Redis session store(Urmi MVP shipped)- Migrate Bonvoice template + outbound API to relay URLs
- Load test at 320-byte / 20 ms frame rate per call
- Document customer-facing API and WebSocket protocol (derived from this plan)
Document maintained by CPaaS platform team. Update when adding providers or changing auth model.