WebSocket Connection
Endpoint
wss://realtime.scriptix.io/v2/realtime
The language query parameter is required. Use a code returned by GET /realtime/languages.
Authentication
You can authenticate in two ways:
Option 1 — HTTP header (recommended for server-side clients)
wss://realtime.scriptix.io/v2/realtime?language=en
x-zoom-s2t-key: YOUR_REALTIME_TOKEN
Option 2 — Query parameter (required for browser clients)
Browsers cannot set custom headers on a WebSocket upgrade. Pass your token as a query parameter instead:
wss://realtime.scriptix.io/v2/realtime?language=en&token=YOUR_REALTIME_TOKEN
Query Parameters
| Parameter | Required | Description |
|---|---|---|
language | Yes | Language code from /realtime/languages. Close 4400 if omitted or invalid. |
token | No | Realtime API token (alternative to x-zoom-s2t-key header). |
name | No | Display name for this session (max 255 characters). Stored on the transcript session record. |
Connection Flow
Client Server
│ │
│── WSS open ────────────────────────────────► │
│◄─ 101 Switching Protocols ────────────────── │
│ │
│── {"action": "start"} ─────────────────────► │
│◄─ {"state": "listening", "session_id": "..."} │
│ │
│── <binary audio frames> ───────────────────► │
│◄─ {"text": "...", "result": [...]} ────────── │ (results)
│ │
│── {"action": "stop"} ──────────────────────► │
│◄─ {"state": "stopped"} ───────────────────── │
│◄─ WS close ───────────────────────────────── │
Code Examples
Python (websockets library)
import asyncio
import websockets
TOKEN = "YOUR_REALTIME_TOKEN"
LANGUAGE = "en"
async def transcribe(audio_bytes: bytes):
uri = f"wss://realtime.scriptix.io/v2/realtime?language={LANGUAGE}"
headers = {"x-zoom-s2t-key": TOKEN}
async with websockets.connect(uri, additional_headers=headers) as ws:
# Start session
await ws.send('{"action": "start"}')
response = await ws.recv()
assert '"state": "listening"' in response, f"Unexpected: {response}"
# Stream audio in chunks
chunk_size = 32 * 1024 # ~1 second at 16kHz
for i in range(0, len(audio_bytes), chunk_size):
await ws.send(audio_bytes[i : i + chunk_size])
# Stop and drain
await ws.send('{"action": "stop"}')
async for message in ws:
print(message)
asyncio.run(transcribe(open("audio.wav", "rb").read()))
JavaScript (Node.js)
import WebSocket from "ws";
const TOKEN = "YOUR_REALTIME_TOKEN";
const LANGUAGE = "en";
const CHUNK_SIZE = 32 * 1024; // ~1 second at 16kHz
function transcribe(audioBuffer) {
return new Promise((resolve, reject) => {
const ws = new WebSocket(
`wss://realtime.scriptix.io/v2/realtime?language=${LANGUAGE}`,
{ headers: { "x-zoom-s2t-key": TOKEN } }
);
const results = [];
ws.on("open", () => {
ws.send(JSON.stringify({ action: "start" }));
});
ws.on("message", (data) => {
const msg = JSON.parse(data);
if (msg.state === "listening") {
// Stream audio
for (let i = 0; i < audioBuffer.length; i += CHUNK_SIZE) {
ws.send(audioBuffer.slice(i, i + CHUNK_SIZE));
}
ws.send(JSON.stringify({ action: "stop" }));
} else if (msg.text) {
results.push(msg.text);
} else if (msg.state === "stopped") {
ws.close();
}
});
ws.on("close", () => resolve(results.join(" ")));
ws.on("error", reject);
});
}
Browser (microphone)
const TOKEN = "YOUR_REALTIME_TOKEN";
const LANGUAGE = "en";
// Browsers cannot set custom headers — use ?token= instead
const ws = new WebSocket(
`wss://realtime.scriptix.io/v2/realtime?language=${LANGUAGE}&token=${TOKEN}`
);
let mediaStream;
ws.addEventListener("open", async () => {
ws.send(JSON.stringify({ action: "start" }));
mediaStream = await navigator.mediaDevices.getUserMedia({ audio: true });
const audioContext = new AudioContext({ sampleRate: 16000 });
const source = audioContext.createMediaStreamSource(mediaStream);
const processor = audioContext.createScriptProcessor(4096, 1, 1);
processor.onaudioprocess = (e) => {
const float32 = e.inputBuffer.getChannelData(0);
const int16 = new Int16Array(float32.length);
for (let i = 0; i < float32.length; i++) {
int16[i] = Math.max(-32768, Math.min(32767, float32[i] * 32768));
}
if (ws.readyState === WebSocket.OPEN) {
ws.send(int16.buffer);
}
};
source.connect(processor);
processor.connect(audioContext.destination);
});
ws.addEventListener("message", (event) => {
const msg = JSON.parse(event.data);
if (msg.text) {
document.getElementById("transcript").textContent = msg.text;
}
});
// Stop
function stopTranscription() {
mediaStream?.getTracks().forEach((t) => t.stop());
ws.send(JSON.stringify({ action: "stop" }));
}
Close Codes
| Code | Reason | Description |
|---|---|---|
4400 | missing_language | ?language= parameter was not provided |
4400 | invalid_language | ?language= was provided but is not available for your organization |
4402 | no_subscription_found | No active realtime subscription for this organization |
4403 | invalid_s2t_token | Token is invalid or not a realtime-type token |
4403 | unauthenticated | No token provided |
4444 | server_shutdown | Server emergency shutdown — reconnect immediately |
4451 | license_missing_entitlement | On-prem license does not include realtime entitlement |
Next Steps
- Message Protocol — all client and server JSON messages
- Audio Formats — how to encode audio before streaming