Skip to content

Realtime audio ​

EVA’s developer transport uses a WebSocket carrying JSON controls and binary audio. It serves published EVA Speech Engine versions.

Connection sequence ​

  1. Create a conversation using your server-side API key.
  2. Connect to the returned stream_url, adding ?token= and its URL-encoded stream_token.
  3. Immediately send session.start with the audio format and server_audio_versions: [1].
  4. Wait for session.started and check server_audio_version.
  5. Send microphone PCM and process incoming events/audio concurrently.
  6. Send session.end when the caller finishes.

The token is single-use and expires according to the bootstrap response. Redact it from logs. The developer route does not emit the dashboard-only stream.authenticated barrier.

Input format ​

Send raw PCM signed 16-bit little-endian, 16,000 Hz, one channel as binary messages. Do not send MP3, Opus, a WAV container, or base64 text. Resample device audio before sending.

For example, a 20 ms chunk contains 320 samples / 640 bytes. Pace chunks in real time and bound your queue so delayed audio cannot accumulate indefinitely.

JSON controls include:

TypeUse
session.startInitial handshake; see quickstart
session.endFinish the conversation
playback.completedReport that a generation’s audio has actually finished playing; include generation_id

Input stream telemetry and generation envelopes used by the dashboard are optional extensions; raw PCM is accepted by the developer stream.

Assistant audio framing ​

Offer version 1 with "server_audio_versions": [1]. A session.started response with no supported version indicates a negotiation failure. Read the output format from the event’s audio_format.

Every version-1 binary output message begins with this little-endian header:

Byte offsetSizeMeaning
04ASCII magic EVAO
41Version 1
51Flags, currently 0
62UTF-8 generation ID length, 1–128
84Sequence number, starting at 1 per generation
12VariableGeneration ID bytes
Following IDRemaining bytesPCM16 audio, nonempty and an even byte count

A decoder for Node.js:

js
export function decodeEvaAudio(buffer) {
  if (buffer.length < 12 || buffer.toString('ascii', 0, 4) !== 'EVAO' ||
      buffer[4] !== 1 || buffer[5] !== 0) throw new Error('Invalid EVA audio');
  const idLength = buffer.readUInt16LE(6);
  const sequence = buffer.readUInt32LE(8);
  const end = 12 + idLength;
  if (idLength < 1 || idLength > 128 || sequence < 1 || end >= buffer.length ||
      (buffer.length - end) % 2 !== 0) throw new Error('Invalid EVA audio');
  const generationId = new TextDecoder('utf-8', { fatal: true })
    .decode(buffer.subarray(12, end));
  return { generationId, sequence, pcm: buffer.subarray(end) };
}

Track sequence numbers within each generation. Do not play the header or ID as audio. Omitting server_audio_versions selects a transitional raw-PCM mode; use version 1 for new integrations so stale generations can be identified.

Events and interruption ​

JSON events include conversation_id and a type. Turn and generation events also identify the relevant turn_id and/or generation_id.

EventClient behavior
session.startedConfigure playback using the negotiated audio format
user.speech_started / user.speech_endedUpdate the caller speaking state
transcript.partialShow provisional user text
transcript.finalUpdate user text using final_revision
turn.user_committedRecord the committed user turn
assistant.generation_startedBegin tracking the new generation
assistant.text_delta / assistant.text_finalUpdate assistant text
assistant.audio_startedAssistant audio is beginning
assistant.interruptedStop and clear queued audio for that generation; ignore later stale chunks
assistant.audio_exhaustedNo more audio will arrive for this generation; drain queued playback
assistant.completedAssistant turn completed; do not cut queued audio prematurely
conversation.ending / conversation.completedTrack conversation lifecycle
conversation.failed / errorSurface a safe failure and finish the session as appropriate

Keep receiving while sending audio. Barge-in requires clearing local playback when interrupted; stopping generation on the server alone does not remove audio already buffered by your client.

Send playback.completed only after the relevant generation’s final queued sample has played. This helps natural ending behavior. A text-final event is not proof that audio has finished.

Failure recovery ​

Treat disconnects as terminal for that stream. Do not reuse a consumed token. Inspect the conversation status before creating a replacement, and apply backoff for transient failures. See errors.

EVA Company · Understanding the conversation.