VSIP
VSIP SDK

Documentation.

Voice Events API — real-time turn detection, identity-gated barge-in, and noise intelligence for any voice agent.

01.0 / OVERVIEW

What the Voice Events API does

Stream raw microphone audio in; get structured JSON events out — in real time, over one WebSocket:

  • Turn detectionturn_start / turn_end with semantic endpointing (knows “I'd like to order… umm” isn't finished)
  • Identity-gated barge-in — only your enrolled speaker can interrupt the agent; a cough or a bystander cannot
  • Speaker verification — an authoritative per-turn verdict (should_respond) so your agent never answers the wrong voice
  • Noise intelligence — cough / breath / laughter / ambient events, never mistaken for speech turns

VSIP does not transcribe, run LLMs, or synthesize speech — it is the sensor layer beside your existing stack. It works with any setup that can forward 16kHz PCM: custom Python backends, LiveKit Agents, Daily Bots, Pipecat, Twilio Media Streams (audio_format=mulaw8k), and speech-to-speech stacks like OpenAI Realtime via listen-only mode=sidecar.

02.0 / IMPLEMENTATION

Quickstart

Create an API key on the API Keys page, then establish your connection:

quickstart.pyCODE PREVIEW
import asyncio, json, pyaudio, websockets

API_KEY = "vsip_YOUR_KEY_HERE"

async def main():
    uri = "wss://api.vsip.online/v1/stream"
    headers = {"Authorization": f"Bearer {API_KEY}"}

    async with websockets.connect(uri, additional_headers=headers) as ws:
        print(json.loads(await ws.recv()))  # session_start

        p = pyaudio.PyAudio()
        mic = p.open(format=pyaudio.paInt16, channels=1, rate=16000,
                     input=True, frames_per_buffer=1600)

        async def send_audio():
            while True:
                await ws.send(mic.read(1600, exception_on_overflow=False))
                await asyncio.sleep(0)

        asyncio.create_task(send_audio())

        async for raw in ws:
            event = json.loads(raw)
            if event["type"] in ("turn_start", "turn_end", "barge_in",
                                 "speaker_verification"):
                print(event)

asyncio.run(main())

Speak — you'll see turn_start, then turn_end when you stop, then speaker_verification. Your first sentence auto-enrolls your voice (speaker_change + lock_status fire mid-turn).

03.0 / AUTHORIZATION

Authentication

Every connection requires an API key, created on the API Keys page. Pass it as a header on the WebSocket handshake:

http_headerCODE PREVIEW
Authorization: Bearer vsip_YOUR_API_KEY

Invalid, revoked, or missing keys are rejected with close code 4001 before the upgrade completes — no data is exchanged.

04.0 / WEBSOCKET SPECS

Connecting & parameters

websocket_urlCODE PREVIEW
wss://api.vsip.online/v1/stream?tick_ms=80&session_id=call-abc-123
ParameterDefaultDescription
tick_ms80Inference tick interval (20–500ms)
audio_formatint16int16 · float32 · mulaw8k (Twilio)
modefullsidecar = listen-only beside speech-to-speech agents
aecclientserver = VSIP cancels echo; tag frames 0x01 mic / 0x02 reference
noise_suppressfalseDeepFilterNet3 denoising (+~15ms)
05.0 / STATE LOOP

The 4-step integration contract

  1. Send 16kHz mono PCM as binary WebSocket frames.
  2. Send {"command":"set_agent_state","value":"speaking"} before your TTS starts playing.
  3. Send {"command":"set_agent_state","value":"idle"} when TTS ends or is interrupted.
  4. On barge_in: stop your TTS immediately, then send idle.
05.1 / VAD STATE MACHINE

VAD & Audio Flow — When to Listen & Pause

Integrating AI voice agents often struggle with VAD timing, resulting in truncated initial words or agents talking over users. Follow the 5-state client orchestration loop implemented in our reference client:

STATE 1

Continuous Audio Stream & Rolling Buffer

Continuously capture microphone audio, convert to 16kHz Int16 PCM, and stream binary frames to VSIP. Store incoming chunks in a rolling memory buffer (last ~2.0 seconds / 100 chunks).

STATE 2

Turn Start & Audio Lookback Slicing

turn_start arrives ~1–2 ticks (80–160ms) after real speech onset. To avoid clipping the user's first word, slice audio from your rolling buffer starting 1.0 second prior to turn_start (or 1.5 seconds if the agent was interrupted).

STATE 3

Turn End & Identity Gating (Pause STT)

On turn_end, pause STT / LLM execution if speaker lock is active (locked). Hold turn audio until speaker_verification is received. Only trigger STT if should_respond === true. If false (bystander or noise), drop the turn and stay quiet.

STATE 4

TTS Playback & State Synchronization

Send {"command":"set_agent_state","value":"speaking"} RIGHT BEFORE audio playback starts (audio.play()). When playback finishes, send {"command":"set_agent_state","value":"idle"}.

STATE 5

Barge-in (Immediate Interruption & Clear)

When barge_in arrives, immediately stop audio playback (audio.pause() or speechSynthesis.cancel()), discard pending speech queues, and send set_agent_state: idle.

05.2 / ONE-SHOT AGENT PROMPTS

AI Agent Integration Prompts

Pick the mode that matches your stack. Copy the entire prompt, paste it into Cursor, Claude, or any AI coding assistant, fill in your API keys, and the agent will build the complete working voice pipeline with zero clarifying questions.

MODE: FULLVSIP + OpenAI Whisper + GPT-4o-mini + OpenAI TTS

VSIP handles all turn detection and identity gating. Your code runs Whisper STT, GPT-4o-mini, and OpenAI TTS. Use this when you want full control over every pipeline step.

FULL_MODE_AGENT_PROMPT.mdCODE PREVIEW
You are an expert voice systems engineer. Build a complete voice agent using the VSIP Voice Events API (Full Mode) + OpenAI APIs. Use Python + asyncio. Do not use any VSIP SDK — connect directly over WebSocket.

━━━ CREDENTIALS (replace these) ━━━
VSIP_API_KEY  = "vsip_YOUR_KEY_HERE"
OPENAI_API_KEY = "sk-YOUR_OPENAI_KEY_HERE"

━━━ ARCHITECTURE ━━━
Microphone (16kHz int16 PCM)
  │
  ├─► VSIP WebSocket (identity sensor + turn detector)
  │     wss://api.vsip.online/v1/stream?mode=full&tick_ms=80
  │     Authorization: Bearer {VSIP_API_KEY}
  │
  └─► On VSIP speaker_verification (should_respond=true):
        buffered audio → OpenAI Whisper STT → text
        text → GPT-4o-mini → reply text
        reply text → OpenAI TTS → speaker output

━━━ EXACT IMPLEMENTATION RULES ━━━

1. AUDIO STREAMING
   - Capture mic at 16000 Hz, 1 channel, int16 PCM using PyAudio
   - Chunk size: 1600 samples (100ms)
   - Send each chunk as a binary WebSocket frame to VSIP
   - Maintain a rolling buffer of the last 50 chunks (~5 seconds)

2. SESSION START
   - Connect with header: Authorization: Bearer {VSIP_API_KEY}
   - First message received from VSIP will be: {"type": "session_start", ...}
   - Log it and begin streaming audio

3. ON turn_start EVENT
   - Record the buffer position at this moment
   - Start accumulating all new mic frames into a dedicated turn_audio list
   - Also prepend 1.0 second of audio from the rolling buffer (25 chunks)
     so the first word of the user is never clipped
   - If the agent was mid-speech when this fires (barge-in case),
     use 1.5 seconds of lookback (37 chunks) instead

4. ON turn_end EVENT
   - Stop accumulating into turn_audio
   - Do NOT call STT yet — wait for speaker_verification

5. ON speaker_verification EVENT
   - If should_respond == False: drop the turn, log "impostor/noise blocked", do nothing
   - If should_respond == True AND the processing lock is NOT held:
       → acquire asyncio.Lock() so only one response runs at a time
       → proceed to step 6

6. WHISPER STT
   - Convert turn_audio (list of bytes) to an in-memory WAV file:
       16000 Hz, 1 channel, 16-bit PCM
   - Call: openai.audio.transcriptions.create(model="whisper-1", file=("audio.wav", wav_bytes, "audio/wav"))
   - If transcript is empty or whitespace: release lock, return

7. GPT-4o-mini CHAT
   - Maintain a conversation history list (system + user/assistant pairs)
   - System prompt: "You are a helpful voice assistant. Keep answers to 1-2 short sentences."
   - Append {"role": "user", "content": transcript} to history
   - Call: openai.chat.completions.create(model="gpt-4o-mini", messages=history, max_tokens=80)
   - Append {"role": "assistant", "content": reply} to history

8. AGENT STATE — BEFORE TTS
   - Send on the VSIP WebSocket: {"command": "set_agent_state", "value": "speaking"}

9. OPENAI TTS PLAYBACK
   - Call: openai.audio.speech.create(model="tts-1", voice="alloy", input=reply, response_format="pcm")
   - The response is raw 24000 Hz 16-bit mono PCM
   - Play it through PyAudio output stream at 24000 Hz
   - Play in 4800-byte chunks (100ms) so barge-in can interrupt mid-sentence
   - Check an asyncio.Event() cancel_flag before each chunk; if set, stop playback

10. AGENT STATE — AFTER TTS
    - Send on the VSIP WebSocket: {"command": "set_agent_state", "value": "idle"}
    - Release the asyncio.Lock()

11. ON barge_in EVENT
    - Set the cancel_flag asyncio.Event() to interrupt TTS immediately
    - Send: {"command": "set_agent_state", "value": "idle"}
    - Clear any pending turn queue

12. ERROR HANDLING
    - Wrap all WebSocket sends in try/except — ignore send errors if connection closed
    - On WebSocket disconnect: log and exit cleanly
    - On Whisper error: log and release lock (do not crash)
    - On GPT error: log and release lock

━━━ DEPENDENCIES ━━━
pip install websockets openai pyaudio numpy python-dotenv

━━━ OUTPUT ━━━
One Python file: agent_full_mode.py
Run with: python agent_full_mode.py
No web UI needed. Console logs show turn events, transcripts, and replies.
MODE: SIDECARVSIP + OpenAI Realtime API (speech-to-speech)

VSIP runs beside OpenAI Realtime as a pure identity and barge-in guardrail. OpenAI Realtime handles STT + LLM + TTS natively. Use this for the lowest-latency speech-to-speech with identity security on top.

SIDECAR_MODE_AGENT_PROMPT.mdCODE PREVIEW
You are an expert voice systems engineer. Build a complete voice agent using VSIP (Sidecar Mode) alongside the OpenAI Realtime API. Use Python + asyncio. Do not use any VSIP SDK — connect directly over WebSocket.

━━━ CREDENTIALS (replace these) ━━━
VSIP_API_KEY   = "vsip_YOUR_KEY_HERE"
OPENAI_API_KEY = "sk-YOUR_OPENAI_KEY_HERE"

━━━ ARCHITECTURE ━━━
Microphone (16kHz int16 PCM)
  │
  ├─► VSIP WebSocket (identity guard only — does NOT transcribe)
  │     wss://api.vsip.online/v1/stream?mode=sidecar&tick_ms=80
  │     Authorization: Bearer {VSIP_API_KEY}
  │     → binary PCM frames
  │
  └─► OpenAI Realtime WebSocket (STT + LLM + TTS — all in one)
        wss://api.openai.com/v1/realtime?model=gpt-4o-realtime-preview
        Authorization: Bearer {OPENAI_API_KEY}
        OpenAI-Beta: realtime=v1
        → input_audio_buffer.append (base64 PCM frames)

VSIP controls WHEN OpenAI responds.
OpenAI Realtime controls HOW it responds (STT + LLM + TTS).

━━━ EXACT IMPLEMENTATION RULES ━━━

1. DUAL CONNECTIONS
   - Open both WebSockets concurrently at startup
   - VSIP: Authorization: Bearer {VSIP_API_KEY}
   - OpenAI Realtime: Authorization: Bearer {OPENAI_API_KEY} + OpenAI-Beta: realtime=v1

2. SESSION CONFIGURATION (send to OpenAI Realtime after connect)
   Send this JSON immediately after connecting to OpenAI Realtime:
   {
     "type": "session.update",
     "session": {
       "modalities": ["text", "audio"],
       "instructions": "You are a helpful voice assistant. Keep answers to 1-2 short sentences.",
       "voice": "alloy",
       "input_audio_format": "pcm16",
       "output_audio_format": "pcm16",
       "turn_detection": null
     }
   }
   Setting turn_detection to null is CRITICAL — VSIP drives turn detection, not OpenAI.

3. DUAL AUDIO STREAMING (run concurrently)
   - Capture mic at 16000 Hz, 1 channel, int16 PCM, 1600 samples per chunk
   - For each chunk:
       → Send raw binary bytes to VSIP WebSocket
       → Send to OpenAI Realtime as:
         {"type": "input_audio_buffer.append", "audio": base64.b64encode(chunk).decode()}
   - This runs in one async loop — same chunk goes to both sockets

4. ON VSIP turn_end EVENT
   - VSIP has detected the user finished speaking
   - Commit the OpenAI audio buffer and request a response:
       Send: {"type": "input_audio_buffer.commit"}
       Send: {"type": "response.create"}
   - Log: "VSIP turn_end → committed to OpenAI Realtime"

5. ON VSIP speaker_verification EVENT
   - If should_respond == False (impostor / noise):
       Send to OpenAI: {"type": "response.cancel"}
       Send to OpenAI: {"type": "input_audio_buffer.clear"}
       Log: "VSIP: impostor blocked — response cancelled"
   - If should_respond == True: do nothing (response.create already sent in step 4)

6. ON VSIP barge_in EVENT
   - User is interrupting the agent mid-response
   - Send to OpenAI: {"type": "response.cancel"}
   - Stop PyAudio output stream playback immediately
   - Send to VSIP: {"command": "set_agent_state", "value": "idle"}
   - Clear any audio playback queue

7. OPENAI REALTIME EVENTS TO HANDLE
   - response.audio.delta:
       Decode base64 delta → raw 24000 Hz int16 PCM bytes
       Write to PyAudio output stream (24000 Hz, int16, mono)
       Before each chunk write, check a cancel_flag — if set, stop writing
   - response.audio.done or response.done:
       Send to VSIP: {"command": "set_agent_state", "value": "idle"}
       Log: "Response complete"
   - error:
       Log the error, do not crash

8. AGENT STATE SYNCHRONIZATION
   - When first response.audio.delta arrives (not yet playing):
       Send to VSIP: {"command": "set_agent_state", "value": "speaking"}
   - When response.done arrives:
       Send to VSIP: {"command": "set_agent_state", "value": "idle"}
   - When barge_in triggers:
       Send to VSIP: {"command": "set_agent_state", "value": "idle"}

9. OUTPUT AUDIO PLAYBACK
   - Open a PyAudio output stream at 24000 Hz, int16, mono
   - Write PCM chunks from response.audio.delta directly to the stream
   - Keep a cancel_flag asyncio.Event(); set it on barge_in, clear it when new response starts

10. ERROR HANDLING
    - Wrap all WebSocket sends in try/except — ignore errors if socket closed
    - If either WebSocket disconnects: log and shut down both cleanly
    - Never let one socket's error crash the other

━━━ DEPENDENCIES ━━━
pip install websockets pyaudio python-dotenv

━━━ OUTPUT ━━━
One Python file: agent_sidecar_mode.py
Run with: python agent_sidecar_mode.py
No web UI needed. Console logs show VSIP events and OpenAI Realtime state.

━━━ KEY DIFFERENCE FROM FULL MODE ━━━
In sidecar mode you do NOT call Whisper, GPT, or TTS separately.
OpenAI Realtime handles all of that internally.
VSIP's only job is: (a) tell you when a turn ends, (b) verify the speaker,
(c) fire barge_in when the user interrupts. Everything else is OpenAI Realtime.
Full Mode — VSIP + Whisper + GPT-4o-mini + TTSSidecar Mode — VSIP + OpenAI Realtime
06.0 / DOWNSTREAM COMMANDS

Control messages

Send JSON text frames on the same WebSocket to control the pipeline mid-session:

CommandPurpose
set_agent_state{"command":"set_agent_state","value":"speaking"|"idle"} — signals TTS state adjustments.
enroll_speakerRegister voiceprint from sample → enrollment_result
toggle_lockLock session control to active speaker.
07.0 / UPSTREAM EVENTS

Events reference

EventWhenKey fields
turn_startSpeech turn opensspeaker_id, audio_start
turn_endSpeech turn endsduration_ms, endpoint
barge_inInterruption detectedspeaker_id, confidence
speaker_verificationIdentity matching completeshould_respond, similarity
08.0 / IDENTITY & GATING

Speaker verification & barge-in

Gate your agent response decisions on speaker_verification.should_respond — not on raw speaker tags. Acoustic feedback cancellation can introduce noise factors, so verify completely before releasing turns.

FAIL-SAFEAgent voice halts immediately when speech energy matches target thresholds.
FAIL-SECUREOnly confirmed matches will trigger the downstream LLM processing pipeline.
09.0 / CLOSE CODES

Errors & close codes

CodeMeaning
4001Unauthorized — missing or invalid token
4029Concurrency quota reached
10.0 / DIAGNOSTICS

Latency metrics

Metricp50p95
Semantic turn_end300ms700ms
Confirmed match verification250ms450ms
11.0 / SDK & ADAPTERS

Python SDK & adapters

The vsip Python SDK (beta) wraps the WebSocket protocol and bakes in every integration pattern on this page — onset-lookback audio slicing, post-lock verification gating, queued turns, and transparent reconnect + resume. You implement one on_turn callback; the SDK handles the rest.

install.shCODE PREVIEW
pip install vsip-sdk              # typed client + VoiceSession
pip install "vsip-sdk[pipecat]"  # + drop-in Pipecat processor
# distribution is vsip-sdk; the import package is still 'vsip'

VoiceSession — one callback

agent.pyCODE PREVIEW
from vsip import VSIPClient, VoiceSession, Turn, int16_to_wav

client = VSIPClient(api_key="vsip_...", url="wss://api.vsip.online/v1/stream")

async def on_turn(turn: Turn):
    if not turn.should_respond:      # verified: not your enrolled user
        return
    text  = await my_stt(int16_to_wav(turn.audio))
    reply = await my_llm(text, interrupted=turn.interrupted_agent)
    await session.speak_gate()       # never talk over an open turn
    await client.agent_speaking()
    await my_tts_play(reply)         # stop this in on_barge_in
    await client.agent_idle()

session = VoiceSession(client, on_turn=on_turn,
                       on_barge_in=lambda e: my_tts_stop())

async with client:
    await session.start()
    async for frame in mic_frames():  # 16kHz mono int16 PCM
        await session.send_audio(frame)

turn.should_respond is the single field you gate your agent on: pre-lock turns dispatch immediately (first-conversation UX); once a speaker is locked, every turn is held until the speaker_verification verdict arrives, so an impostor is never answered.

Pipecat adapter

Drop VSIPProcessor between your STT and LLM stages for identity-gated interruption and verification gating inside a Pipecat pipeline — impostor transcriptions never reach your LLM.

pipeline.pyCODE PREVIEW
from vsip import VSIPClient
from vsip.adapters.pipecat import VSIPProcessor

vsip = VSIPProcessor(VSIPClient(api_key="vsip_...", url="wss://.../v1/stream"))

pipeline = Pipeline([
    transport.input(),
    stt,
    vsip,              # ← identity-gated barge-in + verification gating
    context_aggregator,
    llm,
    tts,
    transport.output(),
])

LiveKit Agents plugin

Plug VSIPVAD in as the session's turn detector — VSIP's semantic endpointing and identity-gated barge-in drive the agent's turn-taking, so a cough or bystander can't interrupt.

agent.pyCODE PREVIEW
from livekit.agents import AgentSession
from vsip import VSIPClient
from vsip.adapters.livekit import VSIPVAD

vsip_vad = VSIPVAD(VSIPClient(api_key="vsip_...", url="wss://.../v1/stream"))

session = AgentSession(vad=vsip_vad, stt=..., llm=..., tts=...)

Twilio Media Streams

VSIP ingests Twilio's μ-law natively. Bridge a phone call to a VSIP session and drive your telephony agent from its events — clear is sent on barge-in to stop buffered playback.

twilio.pyCODE PREVIEW
from vsip import VSIPClient
from vsip.adapters.twilio import TwilioBridge

client = VSIPClient(api_key="vsip_...", url="wss://.../v1/stream",
                    audio_format="mulaw8k")
bridge = TwilioBridge(client, on_turn_end=..., on_verification=...)
await bridge.run(ws.iter_text(), send=ws.send_text)  # FastAPI/Starlette WS

JavaScript / TypeScript

Browser + Node 18+. Same VoiceSession patterns, fully typed.

agent.tsCODE PREVIEW
import { VSIPClient, VoiceSession, type Turn } from "vsip-sdk";

const client = new VSIPClient({ apiKey: "vsip_...", url: "wss://.../v1/stream" });
const session = new VoiceSession(client, {
  onTurn: (t: Turn) => { if (t.shouldRespond) respond(t.audio); },
  onBargeIn: () => myTts.stop(),
});
await client.connect();
session.start();
Python · availablePipecat · availableLiveKit · availableTwilio · availableJavaScript · available
12.0 / VOICE BIOMETRICS

Speaker Profile API

A separate REST API (Product 3): enroll a voiceprint once, then verify (1:1 — “is this Alice?”) or identify (1:N — “which known speaker is this?”) from a short clip. Authenticated with the same vsip_ API key.

profiles.pyCODE PREVIEW
from vsip import ProfilesClient

profiles = ProfilesClient(api_key="vsip_...", base_url="https://api.vsip.online")

# enroll (>=3s clean speech; wav/mp3/int16/float32/mulaw8k)
p = await profiles.enroll("alice", wav_bytes, audio_format="wav",
                          consent_obtained=True, retention_days=365)

# 1:1 verify
r = await profiles.verify(p["profile_id"], sample_bytes, audio_format="wav")
# -> {"match": true, "score": 0.82, "threshold": 0.75, ...}

# 1:N identify
who = await profiles.identify(sample_bytes, audio_format="wav")

Enrollment is quality-gated (rejects too-short / silent / noisy audio with a machine-readable audio_quality code). Every response carries score + threshold + decision; pass a per-request threshold to tune strictness. Enroll is free; verify/identify are metered against a monthly tier allowance.

NOT ANTI-SPOOFINGVoiceprints verify voice similarity, not liveness — a high-quality synthetic clone or replay can pass. Pair with your own liveness/anti-spoof checks for security-critical use.

Persistent identity in streams

Enroll once via REST, then open a stream with ?profile_id=<id> — the session locks to that voice from the first word (no in-session enrollment). One voiceprint, both products.

13.0 / OFFLINE ANALYSIS

Batch Audio Analytics

The offline counterpart to the streaming API (Product 4): upload a recorded file, get back a structured conversation timeline — turns, per-speaker talk-time, silences, noise events, and summary stats. Async job model: submit returns a job_id; poll for the result.

analyze.pyCODE PREVIEW
from vsip import AnalyzeClient

analyze = AnalyzeClient(api_key="vsip_...", base_url="https://api.vsip.online")

job = await analyze.submit(open("call.wav", "rb").read(), "call.wav")
result = await analyze.wait(job["job_id"])   # polls until done

print(result["summary"]["talk_time_ratio"])  # {"speaker_0": 0.62, ...}
print(result["turns"], result["silences"], result["noise_events"])

Same pipeline as the live API, driven by an audio-position clock so timing is measured in audio-time. Metered by audio-minutes analyzed against a monthly tier allowance; jobs and results persist and are retained for 30 days. Ideal for call-center QA, meeting analytics, and compliance review over recorded archives.

REST: POST /v1/analyze (multipart, ≤100 MB) → GET /v1/analyze/{job_id} GET /v1/analyze/usage.