Overview
WebSocket voice agents stream audio over a single persistent WebSocket connection. Coval can exchange raw binary PCM frames or JSON envelopes that wrap base64-encoded PCM, MP3, or G.711 mu-law audio, plus configured non-audio events (cart updates, session signals) the agent emits. Use this connection type for voice agents that:- Stream audio over WebSocket rather than SIP, WebRTC, or HTTP.
- Receive Coval audio at a fixed sample rate and return PCM, MP3, or G.711 mu-law audio.
- Optionally send structured side-events, such as cart updates or session status messages, alongside audio.
Connection modes
In HTTP-first mode, Coval makes the configured HTTP request, extracts the WebSocket URL using
websocket_url_response_path, and opens the audio WebSocket against that URL.
In TwiML webhook mode, Coval calls the voice webhook with Twilio-style call parameters, reads the WebSocket URL and custom parameters from the returned TwiML, then starts a Twilio-shaped media stream. See TwiML voice-webhook setup.
Authentication
WebSocket voice agents authenticate during the WebSocket upgrade.- Authorization header — set
authorization_headerto the auth value Coval should send during the WebSocket upgrade. Values likeBearer <ACCESS_TOKEN>andBasic <BASE64_CREDENTIALS>are sent as theAuthorizationheader value. Values likeX-API-Key <KEY>are sent as theX-API-Keyheader. - Query-string token — when the agent only supports browser-style auth, encode the token directly in the
endpoint, for examplewss://example.com/ws?token=.... - Custom headers —
custom_headersaccepts additional upgrade headers. In the UI, add header name/value rows. Through the API, sendmetadata.custom_headersas a JSON object or as a JSON-encoded object string, for example{"X-Foo":"bar"}or"{\"X-Foo\":\"bar\"}".
authorization_header; use custom_headers for additional named headers. Tokens included directly in the endpoint query string may be visible anywhere URLs are logged, so prefer authorization_header when the agent supports it.
Audio transport
Audio can be exchanged as raw PCM bytes or as JSON envelopes containing a base64-encoded audio payload. The default JSON shape isaudio_chunk / data; the JSON audio preset uses audio_message / audio_bytes, and the Twilio Media Streams preset uses media / media.payload. These JSON shapes are configurable per agent, and setting send_audio_template to exactly {{audio_data}} makes outbound audio raw bytes instead.
The JSON audio preset uses:
- Codec: PCM (linear)
- Sample rate: 16 000 Hz
- Bit depth: 16-bit
- Endianness: little-endian
- Channels: 1 (mono)
- Recommended frame duration for peer implementations: 20-100 ms
audio_message_type_value to identify the agent frames that contain inbound audio, and use send_audio_template to shape Coval-originated audio frames. For the JSON audio preset, Coval sends audio_message frames with sender: "USER" and the agent should send its own audio_message frames with sender: "AI".
Audio format fields
The default receive rate is higher than the send rate because many voice integrations return higher-rate audio while receiving 16 kHz audio from Coval. Paths use dot notation for nested fields, for example
payload.audio.data. Match send_sample_rate_hertz / receive_sample_rate_hertz to the agent’s actual stream format; mismatched sample rates can cause speed, pitch, or quality issues.
HTTP-first setup fields
These fields apply whenconnection_mode is http_first:
TwiML voice-webhook setup fields
These fields apply whenconnection_mode is twiml_webhook:
twilio_from_number and twilio_to_number are fixed on the agent configuration. They are not currently resolved from test-case inputs or agent attributes.
TwiML webhook mode requires audio_encoding: ulaw and 8 000 Hz send and receive sample rates. Coval applies those values when they are omitted, but rejects explicit values that do not match the Twilio Media Streams format.
Handshake
With the default
message_type_path of type, a direct-mode ready message looks like:
message_type_path, Coval uses that same path to find the ready-message type.
Per-simulation placeholders
Coval resolves the following placeholders once for each WebSocket voice simulation:
Connection setup fields are
initialization_json in direct and HTTP-first modes, custom_headers in every mode, http_request_body and http_headers in HTTP-first mode, and voice_form_fields and voice_http_headers in TwiML webhook mode. Send templates are send_audio_template, send_media_template, and send_dtmf_template.
The generated stream and call SIDs are fresh for every simulation. They are Twilio-shaped transport identifiers, not aliases for Coval’s simulation output ID.
For WebSocket simulations, Coval returns the same generated value in both completed-result lookup paths:
- Get run:
run.results.call_sids[simulation_output_id] - Get simulation:
simulation.call_sid
run.results.call_sids contains one entry per WebSocket simulation output that has a generated CallSid. Historical outputs and other transports are omitted from that map. With the Python SDK, read the value from response.run.results.call_sids[simulation_output_id] after the run reaches COMPLETED.
To correlate an external interaction with Coval in both directions, persist the run ID, simulation output ID, and CallSid when the connection starts. For a direct or HTTP-first WebSocket connection, include them in initialization_json:
CallSid as a standard Twilio-style webhook field. The webhook can store that three-ID mapping before it returns TwiML. If the WebSocket server also needs the Coval IDs, return them as <Parameter> values under <Stream>; Coval includes returned parameters in the generated start.customParameters object.
Non-audio event capture
Many voice agents emit side-events alongside the audio stream — cart updates, transcript fragments, session telemetry. By default, Coval ignores non-audio JSON messages. To tell Coval which message types to accept, set:event_type— the value atmessage_type_path(for examplesystem_notify).event_name— the optionaleventfield from the payload (for exampleocb:cart-updated).payload— the full parsed JSON message.
message_type_path is action and non_audio_event_message_types includes system_notify, this inbound message is accepted as a non-audio event:
websocket_event entries. Transcript-based metrics, including LLM judge metrics, see JSON that includes event_type, event_name, and the full payload, so they can evaluate structured side-channel data such as cart contents, selected menu items, modifiers, quantities, and prices alongside the spoken conversation.
Media (image) frames
Voice WebSocket simulations can attach images from a test case mid-conversation.send_media_template controls the outbound shape:
{{media_data}}is required.{{media_name}}and{{mime_type}}are optional placeholders.- If the template is exactly
{{media_data}}, Coval sends raw bytes. - Otherwise, Coval base64-encodes the image and substitutes it into your JSON template.
DTMF frames
send_dtmf_template controls how Coval sends keypad digits when a persona uses DTMF. The template must contain {{digit}}. Coval sends one frame per digit.
The Twilio Media Streams preset uses:
Examples
Initialization payload:JSON audio preset
The agent UI ships aJSON audio preset that fills the metadata for JSON audio WebSocket agents. It sets:
authorization_header to Bearer <ACCESS_TOKEN> after picking the preset if the agent requires auth (most production endpoints do).
Twilio Media Streams preset
Use the Twilio Media Streams configuration preset when you already know thewss:// endpoint and want Coval to connect directly. In the agent editor:
- Choose WebSocket as the connection type.
- Leave Connection Mode set to Direct WebSocket.
- Set Configuration Preset to Twilio Media Streams.
- Enter the WebSocket endpoint and any custom parameters in Initialization JSON.
start.customParameters after selecting the preset when your endpoint needs fixed connection metadata. The preset does not make a Twilio voice-webhook request; use TwiML webhook mode when the endpoint and custom parameters should come from your voice webhook.
TwiML voice-webhook setup
TwiML webhook mode follows the voice-webhook portion of a Twilio Media Streams connection:- Coval generates a
CallSidin the formCAplus 32 hexadecimal characters. - Coval calls
voice_urlwithCallSid,AccountSid,From,To,Direction=inbound,CallStatus=ringing, and anyvoice_form_fields. - The webhook returns TwiML containing a bidirectional
<Connect><Stream>element. - Coval opens the returned
wss://URL and sends aconnectedevent followed by astartevent withsequenceNumber: "1". Thestart.customParametersobject contains the TwiML<Parameter>values. - Coval streams G.711 mu-law audio at 8 000 Hz using the configured media and DTMF templates.
<Start><Stream> is a one-way audio fork and is rejected. The <Stream> URL must use wss://.
Configure in the agent editor
- Create or edit a voice agent and choose WebSocket as the connection type.
- Under Connection Mode, choose TwiML Voice Webhook.
- Enter the Voice Webhook URL and choose
GETorPOST. - Add any voice-webhook headers, form fields, Twilio Account SID, and
From/Tovalues your endpoint expects. - If the returned WebSocket URL requires authentication, configure Authorization Header or Custom Headers under WebSocket Connection.
Configure through the Agents API
You can also configure TwiML webhook mode through the Agents API, which is useful for automated agent setup. The following request creates a complete Twilio-shaped WebSocket voice agent. Replace the example URLs, numbers, and custom fields with values for your endpoint:initialization_json and metadata.endpoint are not used in this mode. Coval builds the initialization events from the returned TwiML and connects to its <Stream> URL.Setup
- Prepare the agent endpoint. Confirm
wss://is reachable, audio format matches the configuration above, and decide whether the agent requires Bearer auth. - Create the agent in Coval. Open the Agents page in your Coval org, choose WebSocket as the connection type, and select the connection mode that matches your endpoint. For a voice webhook, choose TwiML Voice Webhook and fill in the webhook fields. You can also use the Agents API for automated setup.
- Smoke test. Build a small test set with a single voice persona and run a simulation. The transcript should show alternating turns, the result page should expose usable audio, and any configured side-events should be available to transcript-based metrics.
How simulations work
- Coval connects directly, performs HTTP-first setup, or calls the configured TwiML voice webhook to obtain the WebSocket URL.
- Coval sends the configured initialization payload. In TwiML webhook mode, it sends the generated
connectedandstartevents instead. - If
handshake_ready_message_typeis set, Coval waits for the ready message before sending audio. - Coval streams persona audio outward using
send_audio_templateat the configured sample rate: raw bytes for{{audio_data}}, or JSON text frames for any JSON template. - Inbound binary frames or matching JSON audio frames are decoded and resampled if needed.
- Inbound non-audio JSON messages whose type is in
non_audio_event_message_typesare accepted; unconfigured non-audio messages are ignored. - When the persona finishes, Coval closes the WebSocket cleanly.
Sending Traces
Direct and HTTP-first WebSocket agents can send traces to Coval. Set Custom Headers to{"X-Coval-Simulation-Id": "{{simulation_id}}"}. Read that header during the WebSocket upgrade and use its value as the X-Simulation-Id header when exporting OTLP spans. See WebSocket agents for the full setup.
Troubleshooting
Empty transcript with audio frames flowing. Check thataudio_message_type_value matches the agent’s field, that audio_data_path points at the base64 payload, and that audio_encoding matches the wire format.
Inbound audio sounds half-speed or distorted. Confirm receive_audio_channels. JSON PCM that arrives mono should be configured with receive_audio_channels: 1; the historical default 2 averages two channels and halves the apparent rate when the source is mono.
Cart events / status messages look ignored. Add the action value to non_audio_event_message_types. Without it, Coval ignores non-audio JSON messages.
Auth failures during handshake. Verify the authorization_header value, or move the token to a ?token=... query string when the agent only supports browser-style auth.
TwiML response is rejected. Confirm the voice webhook returns valid XML with <Response><Connect><Stream url="wss://...">. A <Start><Stream> response is one-way and cannot carry the simulated user’s audio back to the agent.
Twilio-shaped stream connects but no audio is exchanged. Confirm mu-law at 8 000 Hz, leave the ready-message handshake disabled, and verify the media paths are event and media.payload. The agent editor applies these transport settings when you select TwiML Voice Webhook. For API configuration, include the Twilio media and DTMF templates shown above.
Connection refused locally. Tunnel the agent’s ws:// server through ngrok or Cloudflare Tunnel and use the resulting wss:// URL as the agent endpoint.
https:// URL, use the corresponding wss:// URL in Coval. Update the agent configuration when the tunnel URL changes, or use a reserved tunnel domain for a stable endpoint.
Unreadable audio or media payloads. For JSON audio/media templates, Coval substitutes base64 data into {{audio_data}} / {{media_data}}; for raw templates, the agent must expect raw PCM or media bytes. Verify the JSON is valid, the configured message fields match the agent payload, audio_encoding is correct, and send_media_template includes {{media_name}} / {{mime_type}} when the agent needs file metadata.
Timeouts or no response. Confirm the agent keeps the WebSocket open for the whole conversation, processes incoming audio frames without blocking, sends audio responses in the configured shape, and logs initialization / ready messages while testing.
Best practices
- Pick the JSON audio or Twilio Media Streams preset instead of hand-filling fields when one matches your endpoint. It keeps the metadata canonical for the agent shape.
- Mirror the agent’s sample rate exactly in
send_sample_rate_hertz/receive_sample_rate_hertz. Resampling is supported but degrades audio. - Capture the side-events you care about by adding their
actionvalues tonon_audio_event_message_types. Don’t silently rely on the agent emitting them. - Keep the agent’s WebSocket handler long-lived and avoid closing the connection while the simulation is active.
- Log initialization payloads, ready messages, and payload parsing errors during initial setup.
- Rotate Bearer tokens on a schedule; Coval re-reads the value at every connection setup.