Skip to content
denthub STTDeveloper Platform

Realtime speech-to-text

Stream microphone or file audio over a WebSocket and receive final English transcripts per utterance — in real time.

Your appaiaaskey authStream GatewayVADdensperen STT

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:

  1. Get a license key — denthub-issued, scoped to your account.
  2. Open the WebSocket and send a start frame.
  3. Stream PCM16 audio and read result messages.
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).

Language is English. This platform serves the English model (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.

ParamRequiredNotes
keyRequireddenthub-issued license key
lang=enFixedEnglish STT model — always en on this platform
backend=densper3FixedLow-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.

If your 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

EventMeaningAction
warning: SLOW_CONSUMERYou're sending audio faster than the gateway can processSlow down, or check your network
utterance_dropped (STT_OVERLOAD)The STT backend was overloaded — this utterance was skippedShow a gap; keep streaming
error: STT_TIMEOUT (retryable)The STT request timed outThe gateway retries automatically — wait
close 1001Server going away / drainingReconnect 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 the text fields.
  • Assert the transcript, the utt_index count, and the start_ms/end_ms alignment.

Prefer to poke it by hand first? Open the Playground →