speech.started and speech.completed are optional to ingest, and not strictly bracketed around their utterance’s binary frames. See Event ordering for details.Overview
Bluejay supports real-time, bidirectional voice over WebSocket using CHIRP, a transport-only protocol for exchanging raw audio and optional control events between Bluejay and your server. CHIRP (Conversational Handoff for Inter‑agent Realtime Protocol) is a novel real-time A2A communication protocol, enabling conversational agents to interact over websockets over a standard medium of communication. The bare minimum integration is accepting a WebSocket connection, validating auth, and exchanging raw PCM binary frames. The four text event types (speech.started, speech.completed, session.error, mark) are all optional from your side. Bluejay sends speech.started / speech.completed automatically and echoes any mark you send, but your server can safely ignore every text frame and still have a fully working integration.
When to use WebSocket
Use a WebSocket integration when:- Your agent does not have a phone number.
- You already have a real-time audio pipeline and want to connect it directly to Bluejay.
- You need bidirectional streaming with lower latency than telephony.
- Your system handles raw PCM audio natively.
Quick reference
- Format: 16 kHz mono
pcm_s16lein WebSocket binary frames. - Minimum server: accept WS, validate Basic auth, read binary, write binary.
- Text events are optional from your side. Bluejay emits
speech.started/speech.completedaround Digital Human utterances — not strictly bracketed, see Event ordering. - Barge-in: send
speech.startedwhile the Digital Human is speaking. - Playback acks: send a
markafter your audio; Bluejay echoes the samemarkback once that audio has played out. See Playback acknowledgments. - Hang up: close the WebSocket with code
1000.
Sample server implementations
Minimal servers that Bluejay can connect to. Each one accepts the WS upgrade, validates Basic auth, and echoes received audio back. Replace the echo with your actual audio source and sink.- Basic (audio only)
- With text events
The minimum integration. Handles binary audio frames and ignores everything else.
Simulation result ID
Bluejay sendsX-Simulation-Result-Id as an HTTP header on the WebSocket upgrade request. Capture it if you want to POST tool calls or traces back to /v1/update-simulation-result after the call.
Message formats
CHIRP uses two WebSocket frame types: binary for audio (required) and text for control events (optional). A minimum integration only needs binary.- Binary (audio)
- speech.started (optional)
- speech.completed (optional)
- session.error (optional)
- mark (optional)
Each binary frame is raw audio samples. Both sides send and receive them.
Error handling
If you send a
session.error, Bluejay logs it and surfaces it in test_result.metadata. The connection stays open unless you also close it.
How it works
Connection lifecycle
1
Bluejay opens the WebSocket
Bluejay dials the URL you configured on your Agent (e.g.
wss://your-host/voice) and sends an HTTP upgrade with an Authorization: Basic <base64(user:pass)> header.2
Your server accepts or rejects
- Valid credentials: respond with
HTTP 101 Switching Protocolsand the WS is live. - Invalid credentials: respond with
HTTP 401. Bluejay marks the run asREJECTED.
3
Exchange audio
- When you want to send audio, send it as a WebSocket binary frame of raw
pcm_s16leat 16 kHz mono. - When you receive a binary frame from Bluejay, play it back as raw
pcm_s16leat 16 kHz mono. That is the Digital Human’s voice.
4
(Optional) React to text events
Bluejay emits
speech.started / speech.completed text frames around every utterance. You can ignore them or use them to drive UI. They are not strictly bracketed around their audio — see Event ordering.5
Hang up
Close with code
1000 for a normal end. Bluejay will also close 1000 when the test run completes.Event ordering
speech.started and speech.completed are derived from voice-activity detection, which runs on an independent clock from the audio reader. They are not a precise timestamp for when an utterance’s binary audio starts or ends on the wire.
In practice, you should expect this skew:
Why: VAD needs an analysis window before it can declare that speech has started, and it declares end-of-speech before the underlying audio stream buffer fully drains.
What this means for your server:
- Do not assume binary frames arrive only between
speech.startedandspeech.completedfor a givenutterance_id. A few frames on either side are normal. - Do not use
speech.completedas a hard signal to stop playing audio. Continue playing binary frames as they arrive; end-of-utterance on the wire is when frames stop (or a newspeech.startedwith a differentutterance_idarrives). - Do use
utterance_idto group audio with its speech events for logging, UI, or analytics, but treat the association as best-effort around the boundaries. - Minimum-integration servers (binary only) are unaffected — they never see these events.
Sample session
Barge-in
If you run your own VAD or push-to-talk, sendspeech.started while the Digital Human is mid-utterance to interrupt it. There is no separate interrupt message; speech.started is the interrupt.
Playback acknowledgments
If your server needs to know when audio it sent has finished playing to the Digital Human, send amark text frame right after that audio. Bluejay echoes a mark with the same name once all audio you sent before it has played out. This mirrors the playback-acknowledgment pattern from telephony media streams, reduced to a single matching field.
Use it to drive silence-detection timers, measure round-trip playback latency, or coordinate interruption handling.