Subchapter 7.2
references/realtime-client-side.mdMarkdown6 KBView on GitHub
Stream audio from the browser directly to ElevenLabs for real-time transcription.
# React
npm install @elevenlabs/react@latest @elevenlabs/elevenlabs-js@latest
# JavaScript
npm install @elevenlabs/client@latest @elevenlabs/elevenlabs-js@latestWarning: Always use the
@elevenlabs/*namespace for client-side packages.
Client-side streaming requires a single-use token to protect your API key. Generate tokens on your backend:
import { ElevenLabsClient } from "@elevenlabs/elevenlabs-js";
const elevenlabs = new ElevenLabsClient({
apiKey: process.env.ELEVENLABS_API_KEY,
});
app.get("/scribe-token", yourAuthMiddleware, async (req, res) => {
const token = await elevenlabs.tokens.singleUse.create("realtime_scribe");
res.json(token);
});Note: Single-use tokens expire after 15 minutes.
import { useScribe, CommitStrategy } from "@elevenlabs/react";
function TranscriptionComponent() {
const [transcript, setTranscript] = useState("");
const scribe = useScribe({
modelId: "scribe_v2_realtime",
commitStrategy: CommitStrategy.VAD, // Auto-commit on silence for mic input
includeLanguageDetection: true,
onPartialTranscript: (data) => {
// Show live feedback as user speaks
console.log("Partial:", data.text);
},
onCommittedTranscript: (data) => {
// Final transcript for this segment
setTranscript((prev) => prev + data.text);
},
});
const startRecording = async () => {
const tokenResponse = await fetch("/scribe-token");
const { token } = await tokenResponse.json();
await scribe.connect({
token,
microphone: {
echoCancellation: true,
noiseSuppression: true,
autoGainControl: true,
},
});
};
const stopRecording = () => {
scribe.disconnect();
};
return (
<div>
<div>Status: {scribe.status}</div>
<button onClick={startRecording}>Start</button>
<button onClick={stopRecording}>Stop</button>
<p>{transcript}</p>
</div>
);
}Important: The default commit strategy is
CommitStrategy.MANUAL, which requires you to callscribe.commit()explicitly. For microphone input, always setCommitStrategy.VADso the server auto-commits when silence is detected. Without this, committed transcripts will never fire and the connection may drop.
| Status | Meaning |
|---|---|
"disconnected" | No active connection |
"connecting" | Connection is being established |
"connected" | Connected and ready to receive audio |
"transcribing" | Actively processing speech (transitions from "connected" when audio is detected or VAD commits) |
"error" | An error occurred |
Important: When checking if the session is active, always check for both
"connected"and"transcribing". The status transitions to"transcribing"during speech processing, so checking only"connected"will cause UI elements (buttons, waveforms, indicators) to incorrectly reset mid-session.
// Correct - handles both active states
const isListening = scribe.status === "connected" || scribe.status === "transcribing";
// Wrong - will flicker/reset when VAD commits
const isListening = scribe.status === "connected";import { Scribe, RealtimeEvents } from "@elevenlabs/client";
async function startTranscription() {
const tokenResponse = await fetch("/scribe-token");
const { token } = await tokenResponse.json();
const connection = Scribe.connect({
token,
modelId: "scribe_v2_realtime",
includeTimestamps: true,
includeLanguageDetection: true,
keyterms: ["ElevenLabs", "Scribe"],
noVerbatim: true,
microphone: {
echoCancellation: true,
noiseSuppression: true,
autoGainControl: true,
},
});
connection.on(RealtimeEvents.OPEN, () => {
console.log("Connected");
});
connection.on(RealtimeEvents.PARTIAL_TRANSCRIPT, (data) => {
console.log("Partial:", data.text);
});
connection.on(RealtimeEvents.COMMITTED_TRANSCRIPT, (data) => {
console.log("Committed:", data.text);
});
connection.on(RealtimeEvents.COMMITTED_TRANSCRIPT_WITH_TIMESTAMPS, (data) => {
for (const word of data.words) {
console.log(`${word.text}: ${word.start}s - ${word.end}s`);
}
});
connection.on(RealtimeEvents.ERROR, (error) => {
console.error("Error:", error);
});
connection.on(RealtimeEvents.CLOSE, () => {
console.log("Disconnected");
});
return connection;
}keyterms biases realtime recognition toward important terms. noVerbatim removes filler words,
false starts, and disfluencies from committed transcripts. includeLanguageDetection returns the
detected language code in a delayed final transcript event.
Both Scribe.connect and useScribe accept secondaryLanguages for expected additional
languages, entityDetection for entity events, and filterBackgroundAudio to reduce false
activation from background speech and ambient noise. Do not combine filterBackgroundAudio with
includeTimestamps. Enterprise zero-retention sessions can set enableLogging: false.
For file uploads or custom audio sources, encode to PCM-16 and send in chunks:
const chunkSize = 4096;
for (let offset = 0; offset < pcmData.length; offset += chunkSize) {
const chunk = pcmData.slice(offset, offset + chunkSize);
const bytes = new Uint8Array(chunk.buffer);
const base64 = btoa(String.fromCharCode(...bytes));
scribe.sendAudio(base64);
// Simulate real-time streaming
await new Promise((resolve) => setTimeout(resolve, 50));
}
// Finalize transcription
scribe.commit();| Option | Description |
|---|---|
echoCancellation | Remove echo from speakers |
noiseSuppression | Filter background noise |
autoGainControl | Normalize volume levels |
enableLogging: false in Scribe.connect or
useScribe; this disables history features for the session