BYOVA WebSocket Transport

The gateway can run the existing gRPC service, the Webex BYOVA WebSocket API, or both listeners in one process. Both transports use the same configured connectors, router, VAD behavior, handoff mapping, and monitoring process.

The WebSocket transport implements:

Contract

The WebSocket interface is JSON/AsyncAPI, not a second protobuf service. A pinned copy of the official schemas and the known upstream allOf composition defect are in schemas/websocket.

The first /v1/va application message must be VOICE_VA_REQUEST containing a SESSION_START event. The default deadline is 10 seconds. Every later client envelope must use a strictly increasing seq; gaps are allowed. Binary frames, unknown message types, malformed JSON, invalid lifecycle transitions, and sustained queue pressure produce an ERROR and close the socket.

For a partner-initiated transfer or session end, the gateway sends the terminal VOICE_VA_RESPONSE, flushes it, and waits for WxCC to close the conversation socket. It does not close immediately after sending the terminal event. A bounded peer-close timeout prevents an abandoned connection from holding provider state indefinitely. A transfer or session end that a connector delivers asynchronously, after the request that triggered it has returned, enters the same terminal state.

On gateway shutdown, new upgrades receive HTTP 503, every active call is closed with WebSocket code 1001, and each provider session is ended before the listener stops. The drain is bounded (8 seconds by default).

The gateway owns VAD by default even though the socket is full duplex. A connector may opt out through the existing speech-boundary capability, but the gateway and provider must not both detect boundaries for one conversation.

Local dual-protocol demo

Install the runtime requirements and generate the existing gRPC modules. The complete development-only configuration is checked in as config/websocket_local_example.yaml. Its essential transport settings are:

transports:
  mode: "both"
  allow_partial_transport_startup: false
  websocket:
    host: "127.0.0.1"
    port: 8765
    discovery_idle_timeout_seconds: 5
    allow_unauthenticated_local_dev: true

jwt_validation:
  enabled: false

websocket_jwt_validation:
  enabled: false

data_source:
  enabled: false

websocket_data_source:
  enabled: false

The authentication bypass is rejected for non-loopback clients. Never use it for a shared, public, Webex-connected, or production endpoint. For a WebSocket-only local example, use config/websocket_only_example.yaml. It does not create the gRPC server or read any gRPC datasource credentials.

Start the gateway:

source venv/bin/activate
GATEWAY_CONFIG=config/websocket_local_example.yaml python main.py

In another terminal, verify discovery and session start:

venv/bin/python tools/websocket_smoke.py \
  --url ws://127.0.0.1:58765 \
  --mode list
venv/bin/python tools/websocket_smoke.py \
  --url ws://127.0.0.1:58765 \
  --mode session \
  --agent-id "Local Audio: Local Playback"

Webex-connected configuration

Configure independent profiles for the two datasource contracts:

transports:
  mode: "both"
  websocket:
    allow_unauthenticated_local_dev: false

jwt_validation:
  enabled: true
  enforce_validation: true
  datasource_url: "https://gateway.example.com"
  datasource_schema_uuid: "5397013b-7920-4ffc-807c-e8a3e0a18f43"

websocket_jwt_validation:
  enabled: true
  datasource_url: "wss://gateway.example.com"
  datasource_schema_uuid: "a38a10b7-43e4-4676-a076-a7d6dce9387d"

data_source manages the gRPC registration and websocket_data_source manages the WebSocket registration. Combined mode requires separate Service Apps, datasource IDs, URLs, schemas, token providers, and JWT claim validation. The organization administrator OAuth identity used to obtain Service App tokens may be shared, but each transport must have its own Service App credentials. Register the WebSocket datasource with the secure WebSocket origin without /v1/va; the configured JWT URL must exactly match that value. Webex selects the socket and discovery paths from the protocol contract. Public WSS/TLS terminates at the AWS ALB while the Python process exposes separate private listener ports.

Authorize the WebSocket-only Service App for schema a38a10b7-43e4-4676-a076-a7d6dce9387d. Keep the existing gRPC Service App authorized only for its gRPC schema.

For the controlled AWS test pattern, route /v1/va and /v1/listVirtualAgents from the existing HTTPS listener to an HTTP/1.1 target group on the WebSocket application port. Use /health only as that target group’s private process-health check. See AWS Test Deployment Considerations.

Provider audio modes

WebSocket discovery and /v1/va use the same transport eligibility check. Connectors default to gRPC-only. Local Audio and GECX declare both gRPC and WebSocket support; a deployment can restrict either connector with an explicit supported_transports override. Other connectors require a reviewed opt-in. A manually supplied agent ID cannot bypass this filter.

This eligibility is the WxCC-to-gateway transport, not the provider protocol. GECXConnector talks to CES over gRPC even when it is exposed to WxCC through WebSocket. Configure the separate GECXWebSocketConnector and use a distinct agent ID when both the WxCC and CES connections should use WebSocket.

raw_chunk accepts raw provider chunks or strips the container from a complete WAV and emits 100–65,536 byte CHUNK responses followed by an empty-audio FINAL. wav_final emits one complete WAV response. Unsupported media that would require general codec or sample-rate transcoding is rejected.

See WebSocket Live Testing for the local readiness checks, live acceptance matrix, and evidence to collect when resuming testing.