urmi-ai/CPaaS_WEBSOCKET_RELAY_ARCHITECTURE.md
2026-10-02 11:22:40 +05:30

29 KiB
Raw Permalink Blame History

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://, not https://

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.initiated
  • call.answered
  • call.media_started
  • call.ended
  • call.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 voicebotURL to 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_ms
  • relay_auth_failure_total{reason}

15.2 Tracing

  • Trace ID = call_id or session_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

  1. Customer claims a DID via your dashboard/API
  2. Customer registers upstream WebSocket URL
  3. You configure Bonvoice template → wss://media.yourcpaas.com/v1/media/inbound
  4. Customer implements Bonvoice JSON protocol on their WS server
  5. Customer receives start, then media frames; responds with media and optional clear

19.2 Outbound

  1. Customer calls POST /v1/calls with API key
  2. Optional: pre-connect to wss://media.yourcpaas.com/v1/upstream/{session_token}
  3. Relay bridges Bonvoice ↔ customer when callee answers
  4. 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
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:

  • from must match a phone_numbers row with status=assigned and user_id = caller
  • upstream is required when bridge_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

  1. Approve URL scheme and auth model (Section 7, 10)
  2. Implement Phase 1 tunnel with Redis session store (Urmi MVP shipped)
  3. Migrate Bonvoice template + outbound API to relay URLs
  4. Load test at 320-byte / 20 ms frame rate per call
  5. 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.