Realtime speech-to-text
Stream microphone or file audio over a WebSocket and receive final English transcripts per utterance — in real time.
There is a single WebSocket endpoint, gated by your license key. Authentication and session setup happen at the aiaas layer, in front of the Stream Gateway you stream audio to.
The gateway runs voice activity detection (VAD) server-side, so you just stream audio continuously — no need to detect silence or chunk on speech boundaries yourself.
Get started
Three steps to your first transcript:
- Get a license key — denthub-issued, scoped to your account.
- Open the WebSocket and send a
startframe. - Stream PCM16 audio and read
resultmessages.
const key = "YOUR_LICENSE_KEY"; const ws = new WebSocket( "wss://api.denthub.ai/stt/realtime/stream?key=" + key + "&lang=en&backend=densper3" ); ws.binaryType = "arraybuffer"; ws.onopen = () => { // 1) send the start frame (text) before any audio ws.send(JSON.stringify({ type: "start", sample_rate: 16000, codec: "pcm_s16le", channels: 1, lang: "en", backend: "densper3", })); startMic(ws); // 2) then stream raw PCM16 }; ws.onmessage = (e) => { const msg = JSON.parse(e.data); if (msg.type === "result") console.log(`[${msg.utt_index}] ${msg.text}`); }; async function startMic(ws) { const stream = await navigator.mediaDevices.getUserMedia({ audio: true }); const ctx = new AudioContext({ sampleRate: 16000 }); await ctx.audioWorklet.addModule("/pcm-worklet.js"); const node = new AudioWorkletNode(ctx, "pcm-processor"); ctx.createMediaStreamSource(stream).connect(node); node.port.onmessage = (e) => ws.send(e.data); // binary PCM16LE frames }
curl can't stream WS binary frames — use wscat/Postman for manual testing (REST only for /health).
lang=en, backend=densper3). Both are fixed for you — just send audio.Authentication
Your key rides as the key query parameter on the WebSocket URL, because browsers can't set custom headers on a wss:// connection. Keep the key server-side wherever possible; where it must live in browser code, treat it as a short-lived, scoped credential.
| Param | Required | Notes |
|---|---|---|
| key | Required | denthub-issued license key |
| lang=en | Fixed | English STT model — always en on this platform |
| backend=densper3 | Fixed | Low-latency backend with word-level timestamps |
Streaming audio
Send raw PCM16LE, 16 kHz, mono binary frames — no WAV header, no base64, no JSON wrapping.
Any frame size works; ~1024 bytes (32 ms) is a good default. Send continuously — the gateway runs VAD server-side, so you don't need to gate on silence.
AudioContext runs at 48 kHz (the browser default), resample to 16 kHz before sending — otherwise both the audio and its timestamps will be wrong.Handling results
You get one result message per utterance — final only, no partials. Each carries the transcribed text plus recording-relative timestamps, so you can line up an utterance (or a word) against the audio you sent.
{
"type": "result",
"utt_index": 0,
"session_id": "b1f7…",
"abs_start_utc": "2026-07-10T05:12:01.80Z",
"abs_end_utc": "2026-07-10T05:12:05.20Z",
"duration_ms": 3400,
"text": "Patient presents with sensitivity on the upper right quadrant.",
"segments": [
{ "start_ms": 800, "end_ms": 4200,
"text": "Patient presents with sensitivity…", "language": "en" }
],
"words": [ { "word": "Patient", "start_ms": 800, "end_ms": 1180 }, … ],
"status": "ok",
"forced_cut": false,
"continues": false
}Timestamps (start_ms/end_ms, abs_*_utc) come from a sample-count clock, immune to jitter or processing time. Use them — never message arrival time — to place an utterance on your timeline.
Error handling
| Event | Meaning | Action |
|---|---|---|
| warning: SLOW_CONSUMER | You're sending audio faster than the gateway can process | Slow down, or check your network |
| utterance_dropped (STT_OVERLOAD) | The STT backend was overloaded — this utterance was skipped | Show a gap; keep streaming |
| error: STT_TIMEOUT (retryable) | The STT request timed out | The gateway retries automatically — wait |
| close 1001 | Server going away / draining | Reconnect to a new session |
Test your integration
Send a known sample file and assert you get it back: the same audio in should produce the same transcript, utterance count, and timestamps out.
- Prepare a 16 kHz mono PCM16 clip of known speech.
- Stream it, collect every
result, and join thetextfields. - Assert the transcript, the
utt_indexcount, and thestart_ms/end_msalignment.
Prefer to poke it by hand first? Open the Playground →