Skip to main content

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:

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

ParameterRequiredDescription
languageYesLanguage code from /realtime/languages. Close 4400 if omitted or invalid.
tokenNoRealtime API token (alternative to x-zoom-s2t-key header).
nameNoDisplay 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

CodeReasonDescription
4400missing_language?language= parameter was not provided
4400invalid_language?language= was provided but is not available for your organization
4402no_subscription_foundNo active realtime subscription for this organization
4403invalid_s2t_tokenToken is invalid or not a realtime-type token
4403unauthenticatedNo token provided
4444server_shutdownServer emergency shutdown — reconnect immediately
4451license_missing_entitlementOn-prem license does not include realtime entitlement

Next Steps