first commit
This commit is contained in:
commit
efa5080897
127 changed files with 18610 additions and 0 deletions
973
CPaaS_WEBSOCKET_RELAY_ARCHITECTURE.md
Normal file
973
CPaaS_WEBSOCKET_RELAY_ARCHITECTURE.md
Normal file
|
|
@ -0,0 +1,973 @@
|
|||
# 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
|
||||
|
||||
```mermaid
|
||||
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**
|
||||
|
||||
```json
|
||||
{
|
||||
"event": "start",
|
||||
"data": {
|
||||
"stream_id": "<id>",
|
||||
"call_id": "<id>",
|
||||
"from": "<caller>",
|
||||
"to": "<did>"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Media (bidirectional)**
|
||||
|
||||
```json
|
||||
{
|
||||
"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)**
|
||||
|
||||
```json
|
||||
{
|
||||
"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>
|
||||
```
|
||||
|
||||
```json
|
||||
{
|
||||
"autocallType": "5",
|
||||
"destination": "<callee>",
|
||||
"legACallerID": "<did>",
|
||||
"eventID": "<unique_id>",
|
||||
"voicebotProvider": "BONVOICE",
|
||||
"voicebotURL": "wss://<your-relay-host>/v1/media/<session_token>"
|
||||
}
|
||||
```
|
||||
|
||||
Response:
|
||||
|
||||
```json
|
||||
{
|
||||
"responseCode": 200,
|
||||
"responseDescription": "Success",
|
||||
"responseType": "Success",
|
||||
"eventID": "..."
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 5. Relay Component Architecture
|
||||
|
||||
```mermaid
|
||||
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)
|
||||
|
||||
```python
|
||||
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.
|
||||
|
||||
```python
|
||||
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
|
||||
|
||||
```mermaid
|
||||
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
|
||||
|
||||
```mermaid
|
||||
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:
|
||||
|
||||
```json
|
||||
{
|
||||
"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)
|
||||
|
||||
```json
|
||||
{
|
||||
"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
|
||||
|
||||
```mermaid
|
||||
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:
|
||||
|
||||
```python
|
||||
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:**
|
||||
|
||||
```http
|
||||
Authorization: Token 8de97c5932280c84c4bbf36140a1ec7ab7ae0b23
|
||||
Content-Type: application/json
|
||||
```
|
||||
|
||||
**Outbound request body (validated PoC example):**
|
||||
|
||||
```json
|
||||
{
|
||||
"autocallType": "5",
|
||||
"destination": "9149154337",
|
||||
"legACallerID": "7946350706",
|
||||
"eventID": "11A31A12FF1C4455",
|
||||
"voicebotProvider": "BONVOICE",
|
||||
"voicebotURL": "wss://<your-relay-or-ngrok-host>/ws/media"
|
||||
}
|
||||
```
|
||||
|
||||
**Successful outbound response (observed):**
|
||||
|
||||
```json
|
||||
{
|
||||
"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:
|
||||
|
||||
```bash
|
||||
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):
|
||||
|
||||
```bash
|
||||
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**
|
||||
|
||||
```http
|
||||
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`):
|
||||
|
||||
```json
|
||||
{
|
||||
"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.*
|
||||
Loading…
Add table
Add a link
Reference in a new issue