Install
$ agentstack add skill-vobiz-ai-agent-skills-vobiz-audio-streams ✓ scanned · ✓ verified, works with Claude Code, Cursor, and more.
Security review
✓ PassedNo issues found. Passed automated security review. · v0.1.0 How review works →
- ✓ Prompt-injection patterns
- ✓ Secret / credential exfiltration
- ✓ Dangerous shell & filesystem operations
- ✓ Untrusted network calls
- ✓ Known-malicious package signatures
What it can access
- ● Network access Used
- ✓ Filesystem access No
- ✓ Shell / process execution No
- ✓ Environment & secrets No
- ✓ Dynamic code execution No
From automated source analysis of v0.1.0. “Used” means the capability is present in the source — more access means more to trust, not that it’s unsafe.
Verified badge
Passed review? Show it. Paste this badge into your README, it links to the public security report.
Reliability & compatibility
Declared compatibility
Compatibility is declared by the source manifest. End-to-end runtime verification is coming, see below.
We're building live execution health for every listing: tool-call success rate, median latency, uptime, and last-checked timestamps, measured, not self-reported. It isn't live yet, so we don't show numbers we can't stand behind.
How agent discovery & health will work →About
Vobiz Audio Streams skill
Use this for AI voice agents, real-time transcription, custom STT/TTS pipelines, or any workflow that needs raw audio frames rather than just recordings.
How it works
- During a call, your VobizXML answer handler returns a
wss://your-server/wsverb. - Vobiz opens a WebSocket to your server and sends a single
startevent withcallId,streamId, andmediaFormat. - Audio frames flow Vobiz → your server as
mediaevents (~50 per second per track, 20 ms each). - For bidirectional streams, your server sends
playAudioevents back, pluscheckpoint,clearAudio, andstopcontrol messages. - When the call ends, the WebSocket closes - there is no inbound
stopevent.
Two ways to start a stream
| Approach | When to use | |---|---| | **` XML verb** | The call is being set up by your answer handler. Best for AI agents that own the call from the first ring. Bidirectional playback requires this path. | | **REST POST .../Stream/** | The call is already live and you want to attach (or fork) audio mid-call - e.g. start transcription after a transfer. Returns a stream_id` you can later list/get/stop. |
REST streams are forks: bidirectional=true over REST still requires audio_track=inbound. For full duplex voice agents, prefer the XML verb.
REST endpoints
Base: https://api.vobiz.ai/api/v1. Auth headers: X-Auth-ID, X-Auth-Token.
| Op | Method + Path | Notes | |---|---|---| | Start stream | POST /Account/{auth_id}/Call/{call_uuid}/Stream/ | Body needs service_url; returns stream_id (202). | | List streams | GET /Account/{auth_id}/Call/{call_uuid}/Stream/ | Active + stopped, paginated (limit/offset). | | Get stream | GET /Account/{auth_id}/Call/{call_uuid}/Stream/{stream_id}/ | Full Stream object; 404 if unknown. | | Stop one stream | DELETE /Account/{auth_id}/Call/{call_uuid}/Stream/{stream_id}/ | 204; leaves other forks running. | | Stop all streams | DELETE /Account/{auth_id}/Call/{call_uuid}/Stream/ | 204; tears down every fork on the call. |
Audio format
contentType(XML) /content_type(REST):audio/x-l16;rate=8000(default),audio/x-l16;rate=16000,audio/x-l16;rate=24000, oraudio/x-mulaw;rate=8000.- L16 is 16-bit linear PCM, mono. On the wire
media.payloadis network byte order (big-endian) - swap to little-endian before writing a WAV.playAudioyou send should be little-endian L16. - µ-law is fixed at 8 kHz, mono. Recommended for carrier compatibility and lowest bandwidth.
- 20 ms frame = 160 bytes µ-law @ 8 kHz, 320 bytes L16 @ 8 kHz, 640 bytes L16 @ 16 kHz.
- Frames are base64-encoded inside
media.payload. Never send WAV headers - raw samples only. - The format Vobiz reports in
start.mediaFormatis the format you must match in everyplayAudio.
WebSocket events
Vobiz → your server
start- one handshake frame. IDs live inside the nestedstartobject:data.start.streamId,data.start.callId,data.start.mediaFormat.media- base64 audio frame;media.track,media.timestamp,media.chunk,media.payload.playedStream- ack that audio before yourcheckpointfinished playing. Shape is justevent+name(nostreamIdon the WS event).clearedAudio- ack thatclearAudioflushed the playback queue.
Your server → Vobiz (require bidirectional="true")
playAudio- queue a ~20 ms chunk:{ event, media: { contentType, sampleRate, payload } }.checkpoint- mark end of an utterance:{ event, streamId, name }; ack arrives asplayedStream.clearAudio- barge-in flush:{ event, streamId }; ack arrives asclearedAudio.stop- end the stream from your side:{ event, streamId }. Vobiz then runs the next XML element, or hangs up.
HTTP status callbacks (separate channel)
If you set statusCallbackUrl, Vobiz POSTs form-encoded lifecycle events - distinct from WebSocket events:
Event=StartStream- WebSocket established (carriesServiceURL).Event=PlayedStream- checkpoint reached (carriesName); only fires if playback completed.Event=StopStream- stream ended. Only reliably fires for server-initiated stops - not for caller-hangup or mid-call kill.
The authoritative "call is over" signal across all paths is the Event=Hangup POST to your call's hangup_url.
When to search docs
- "Start mid-call over REST" →
audio-streams/start-audio-stream; Stream fields →audio-streams/stream-object - "Stop one fork vs all forks" →
audio-streams/stop-audio-stream,audio-streams/stop-all-audio-streams - "`
attributes / status callbacks / maxRetries" →xml/stream` - "Handshake, codecs, end-of-stream detection" →
xml/stream/initiate - "Full event protocol both directions" →
xml/stream/stream-events - "Playing audio mid-stream" →
xml/stream/play-audio - "Clearing queued TTS on interruption / barge-in" →
xml/stream/clear-audio - "Checkpoint / playback acks" →
xml/stream/checkpoint-event - "Ending the stream from your side" →
xml/stream/stop-event - "Pipe audio to Deepgram / OpenAI Realtime / Pipecat / LiveKit" →
integrations/openai-realtime,integrations/websockets,integrations/pipecat,integrations/livekit
Pitfalls
- No inbound
stopevent. When the call ends, the WebSocket simply closes after the lastmediaframe. Do not wait for{ "event": "stop" }from Vobiz - you will wait forever. Treat WScloseas your end-of-stream signal and flush buffers there. StopStreamcallback is conditional. It fires reliably only when you send an outboundstop. It is not observed for caller-hangup or mid-call kills. Use thehangup_urlEvent=Hangupwebhook as the authoritative end-of-call signal.- IDs are nested. Read
data.start.streamId/data.start.callId, notdata.streamId, on thestartevent. playedStreamis conditional. No ack arrives if aclearAudioor disconnect interrupted the queued audio. Always have a timeout fallback - never block conversation logic on a checkpoint ack.bidirectionalexcludesboth/outbound. Withbidirectional="true"you must useaudioTrack="inbound"(or omit it).audioTrack="both"+bidirectional="true"makes the call hang up with "End Of XML Instructions".keepCallAliveneedsbidirectional="true". WithoutkeepCallAlive, a `with no following elements ends the XML and hangs up instantly. Use it instead of a`.- Endianness bites.
media.payloadL16 is big-endian inbound;playAudioL16 must be little-endian. Mismatched endianness = static/garbled audio. - Format must match
start.mediaFormat. SendingplayAudioat a differentcontentType/sampleRatethan negotiated produces garbled playback. - Never detect end-of-call from
media.payload. All-0xFF(base64 all-/) bytes are just silence and appear mid-call. Use the WScloseevent. - Chunk size matters for barge-in. Send ~20 ms chunks so a
clearAudioflush is responsive. Large chunks add latency thatclearAudiocannot recover. wss://in production.ws://is for local testing only; carriers and Vobiz require TLS in production. The URL must be publicly reachable.stopis terminal. After you sendstop, the WS closes immediately - any queuedplayAudio/checkpoint/clearAudiosends are dropped. Send a farewell +checkpoint, wait forplayedStream, thenstop.- Reconnects.
maxRetries(XML, 0-10, default 0) controls how many times Vobiz retries a failed/dropped WebSocket. Make yourstarthandler idempotent - a reconnect replays a freshstartwith a newstreamId.
Complete bidirectional voice-agent recipe
A minimal full-duplex loop: greet on start, transcribe inbound media, barge-in with clearAudio, respond with playAudio + checkpoint, end with stop.
```javascript Node.js bidirectional agent skeleton const WebSocket = require('ws'); const wss = new WebSocket.Server({ port: 8080 });
wss.on('connection', (ws) => { let streamId = null; let mediaFormat = null; // { encoding, sampleRate } from start let botSpeaking = false; let checkpointTimer = null;
ws.on('message', async (raw) => { const data = JSON.parse(raw);
switch (data.event) { case 'start': streamId = data.start.streamId; // IDs are NESTED mediaFormat = data.start.mediaFormat; // match this in playAudio await speak(ws, await tts('Hi, how can I help?'), 'greeting'); break;
case 'media': { const pcm = Buffer.from(data.media.payload, 'base64'); // big-endian L16 // Barge-in: caller talks while bot is talking. if (botSpeaking && isSpeech(pcm)) { ws.send(JSON.stringify({ event: 'clearAudio', streamId })); botSpeaking = false; } feedTranscriber(pcm); // your STT; on final transcript -> respond() break; }
case 'clearedAudio': // Playback queue flushed; safe to enqueue the new response. break;
case 'playedStream': // Utterance finished. Clear any fallback timer. if (checkpointTimer) { clearTimeout(checkpointTimer); checkpointTimer = null; } botSpeaking = false; if (data.name === 'farewell') { ws.send(JSON.stringify({ event: 'stop', streamId })); } break; } });
ws.on('close', () => { // Canonical end-of-stream. Flush recordings/transcripts here. teardown(streamId); });
async function respond(text, name) { await speak(ws, await tts(text), name); }
async function speak(ws, pcmBuffer, name) { botSpeaking = true; const frame = 320; // 20 ms of L16 @ 8 kHz; adjust to mediaFormat.sampleRate for (let i = 0; i { botSpeaking = false; }, 15000); } });
Pair it with this answer handler:
```xml VobizXML answer_url response
wss://your-server.com/ws
Source & license
This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.
- Author: vobiz-ai
- Source: vobiz-ai/Agent-Skills
- License: MIT
Install and usage instructions live in the source repository linked above.
Reviews
No reviews yet, be the first.
Write a review
Versions
- v0.1.0 Imported from the upstream source.