Baton sends JSON events over the WebSocket. Some every session receives; others you ask for in session.start with signals. Each name below links to its full definition in the reference: /v2/stream or /v2/room.
Every event carries type, seq (counts the events you were sent) and, where it describes a moment in the audio, t in seconds of audio since the session started.
Events every session receives
| Event | What it means | What to do |
|---|---|---|
session.started |
The session is open; it reports the settings in effect, the signals you'll receive, and schema_version. |
Start sending audio. Check eot if you chose an operating point. |
turn.end |
The user has finished their turn. | Let your agent respond. If it carries answer or hint, use them (see below). |
turn.resumed |
The user started speaking again within 2 s of the last turn.end. |
Treat that turn end as cancelled: stop or hold your agent's reply. |
turn.answer |
Which of your expected answers the user gave, for a turn that already ended. Matched to it by turn_t. |
Act on it as you would on an answer on turn.end. Expected answers. |
turn.hint |
Baton's best guess at words in a turn that had speech but no clear words. Matched by turn_t. |
Use it to re-ask or confirm, not to act on directly. Hints. |
warning |
No speech for a while; the session will close soon. | Send audio or session.end. |
session.summary |
Totals for the session, in reply to session.end. |
Close your side. |
Rooms also always receive their transcripts: asr.final (one voice's words that won't change, tagged with the speaker) and asr.flushed (sent after your finalize, once every pending word has been sent).
Events you ask for
Add names to signals in session.start. What you ask for changes only what you are sent, never how turns are decided or what is transcribed. session.started echoes the list in effect; an unknown name closes the session with 1008 (eot is accepted and ignored).
{
"type": "session.start",
"api_key": "baton_...",
"signals": ["vad", "bargein"]
}
| Signal | Events | Use them to |
|---|---|---|
vad |
vad.start, vad.end |
Show that the user is speaking, or duck your agent's audio. vad.end is not a turn end: wait for turn.end. |
bargein |
bargein.onset, bargein.class |
Stop your agent when the user interrupts. bargein.onset says someone started talking over your agent; bargein.class says whether they are taking the floor (claim: stop talking) or just acknowledging (backchannel: keep going). Barge-in. |
partials (rooms) |
asr.partial |
Show live captions. Words may still change; the settled words come in asr.final. |
language (rooms) |
asr.language, language.switched, language.switch_recommended, language.summary |
Follow which language each voice is speaking. Languages. |
A turn, in order
A short exchange on /v2/stream with "signals": ["vad"] and an expect list on the agent's question:
{"type": "vad.start", "seq": 2, "t": 2.4, "speaker": "user"}
{"type": "vad.end", "seq": 3, "t": 2.9, "speaker": "user", "duration": 0.5}
{"type": "turn.end", "seq": 4, "t": 3.2, "speaker": "user", "p_eot": 0.81, "latency_ms": 300, "by": "score", "profile": "cutoff_5pct",
"answer": {"match": "yes", "score": 0.97, "runner_up": "no", "scores": {"yes": 0.97, "no": 0.02, "none": 0.01}}}
Without vad in signals, the same turn is just the turn.end. If the answer isn't ready when the turn ends, turn.end comes without it and a turn.answer with "turn_t": 3.2 follows, typically 2–3 s later.
Times
t is audio time, so it lines up with what you sent, not with when the event arrived. Events normally come in t order, but a vad.start can carry a t a little earlier than an event before it, when Baton dates a turn from where the user actually began.