Realtime Transcription
Real-time transcription streams transcript data while the meeting is in progress. Enable it by selecting a real-time transcription model.
Key Features
- Live transcript streaming via WebSocket connection
- Immediate access to spoken content as it happens
- Suitable for applications requiring real-time processing
- Optional real-time audio streaming for raw audio access
How it works
After creating a meeting bot with a real-time transcription model, or enabling the Realtime features add-on for another model, the response includes websocket_url and websocket_read_only_url for event streaming.
websocket_url: Full access, allows Actions.websocket_read_only_url: Read-only access. This is safe to provide to your customer's front-end directly.
Connect to either URL to receive live updates. The first connected event contains cached transcript and chat history, the latest state for every observed participant, and the current bot status. Subsequent events keep that context current. Only websocket_url accepts actions.
Realtime features also include websocket_audio_url for a separate raw-audio connection.
Manual Recording Start
Set recording_start_mode: "manual" when the bot should join without immediately recording. Manual mode requires either a real-time transcription model or the Realtime features add-on (realtime_audio: true); requests without either are rejected.
After connecting to websocket_url, use the connected snapshot and subsequent events to apply your own participant and consent policy. When your conditions are satisfied, send the start-recording action. Skribby provides the context and action but does not decide whether consent was given.
Realtime Audio Streaming
You can also receive raw audio data in real time through a separate WebSocket connection.
Enabling Realtime Features for Raw Audio
Real-time models include Realtime features by default. To enable the same event, action, and raw-audio bundle with another model, set the backwards-compatible realtime_audio: true field in your request:
{
"transcription_model": "none",
"meeting_url": "https://meet.google.com/abc-defg-hij",
"service": "gmeet",
"bot_name": "Alex from Acme",
"realtime_audio": true
}
The bot response will include a websocket_audio_url field containing the WebSocket URL for receiving audio data.
Using the SDK
// Create bot with Realtime features enabled
const bot = await client.createBot({
transcription_model: 'none',
meeting_url: 'https://meet.google.com/abc-defg-hij',
service: 'gmeet',
bot_name: 'Alex from Acme',
realtime_audio: true,
});
// Get the realtime client (includes audio by default)
const realtimeClient = bot.getRealtimeClient();
// Listen to audio events
realtimeClient.on('audio', (buffer: Buffer) => {
// buffer is 16-bit PCM audio at 16kHz sample rate
processAudio(buffer);
});
await realtimeClient.connect();
// Check audio connection status
console.log('Audio connected:', realtimeClient.audioConnected);
Without Audio Streaming
If you don't need audio streaming, you can get a realtime client without it:
// Get realtime client WITHOUT audio streaming
const transcriptOnlyClient = bot.getRealtimeClient(true);
await transcriptOnlyClient.connect();
Audio Format
The audio data received via the audio event is:
- Format: 16-bit PCM (signed, little-endian)
- Sample Rate: 16kHz
- Channels: Mono
WebSocket Events
Events arrive as JSON-encoded WebSocket messages with this structure:
{
"type": "[event]",
"data": {...}
}
Connected
When you connect, including through websocket_read_only_url, the first connected event contains cached transcript and chat history, the latest state for every observed participant, and the current bot status. Reconnecting reads this server-side snapshot and does not require the bot to resend its history.
{
"type": "connected",
"data": {
"transcripts": [
{
"transcript": "This contains the spoken text.",
"start": 1.23,
"end": 4.56,
"speaker": 0,
"speaker_name": "John Doe"
}
],
"participants": [
{
"participantId": "participant-123",
"participantName": "Ada Lovelace",
"timestamp": 1784550725000,
"lastSeenAt": 1784550725000,
"state": {
"active": true,
"microphone": "muted",
"camera": "off",
"screenshare": "not-sharing"
}
}
],
"chat_messages": [
{
"id": "message-123",
"parent_id": null,
"username": "Ada Lovelace",
"content": "I consent to recording.",
"user_avatar": null
}
],
"status": "waiting_to_record"
}
}
SDK tip: full transcript buffer
If you're using the TypeScript/JavaScript SDK, the RealtimeClient maintains an internal transcript buffer for you:
- On
connect(), the SDK resets the buffer. - When the
"connected"snapshot arrives, the SDK seeds the buffer fromdata.transcripts. - On each
"ts"event, the SDK appends the new segment.
You can read the full transcript so far at any time via realtimeClient.transcript:
const realtimeClient = bot.getRealtimeClient();
realtimeClient.on('ts', (segment) => {
console.log(segment.speaker_name, segment.transcript);
console.log('Transcript so far:', realtimeClient.transcript);
});
await realtimeClient.connect();
Start
The start event indicates that the bot has joined and started recording. Transcript events can follow from this point. See Bot Lifecycle for the joining process.
{
"type": "start"
}
Status Update
The status-update event reports the previous and current bot status.
If new_status becomes finished or not_admitted, a third field called stop_reason will also be included. Refer to the Bot Lifecycle Documentation for all stop reason codes.
{
"type": "status-update",
"data": {
"old_status": "joining",
"new_status": "recording"
}
}
When the bot stops, stop_reason is included:
{
"type": "status-update",
"data": {
"old_status": "processing",
"new_status": "finished",
"stop_reason": "meeting_ended"
}
}
Recording Started
The recording-started event confirms that media capture began. For a manual-start bot, it acknowledges a successful start-recording action. started_at is an epoch-millisecond timestamp. A status-update to recording also confirms the transition.
{
"type": "recording-started",
"data": {
"started_at": 1784550726123
}
}
Transcript
The ts event contains a live transcript segment with timestamps and speaker information.
Early segments may use a generic speaker_name, such as "Speaker 1", while Skribby correlates the audio stream with the platform participant list. Later segments use the participant name when that match is available.
{
"type": "ts",
"data": {
"transcript": "This contains the spoken text.",
"start": 1.23,
"end": 4.56,
"speaker": 0,
"speaker_name": "John Doe"
}
}
Chat Message
The chat-message event is emitted when a new chat message is received.
Zoom only: chat message events include id and parent_id for thread support, and may include a to field for the recipient name. Refer to the Zoom Chat Guide for details on how these fields work across Meetings and Webinars.
{
"type": "chat-message",
"data": {
"id": "msg_8f1c8f0", // Zoom only
"parent_id": "msg_7c2b4a9", // Zoom only
"username": "John Doe",
"to": "Jane Doe", // Zoom only
"content": "Foo bar.",
"user_avatar": null // Either a URL or null. Do not rely on this field for permanent access.
}
}
Field notes:
id: unique message identifier.parent_id: (Optional, Zoom only) parent message ID for threaded replies, otherwisenull.username: name of the sender.to: (Optional, Zoom only) populated when Zoom exposes a recipient. For private messages, this is usually the recipient name. For public messages, it may be"Meeting Group Chat","Everyone", or absent.
Participant Events
Skribby emits realtime events when participants join, leave, speak, or change their meeting state:
participant-tracked: A participant was detected for the first time. This also fires for participants already present when the bot joins.started-speaking: A participant started speaking.stopped-speaking: A participant stopped speaking.participant-left: A participant left the meeting.participant-rejoined: A participant rejoined the meeting.participant-muted: A participant muted their microphone.participant-unmuted: A participant unmuted their microphone.participant-camera-on: A participant turned their camera on.participant-camera-off: A participant turned their camera off.participant-started-screenshare: A participant started sharing their screen.participant-stopped-screenshare: A participant stopped sharing their screen.
Example:
{
"type": "participant-muted",
"data": {
"participantId": "John Doe",
"participantName": "John Doe",
"timestamp": 1750820602963,
"state": {
"active": true,
"microphone": "muted",
"camera": "off",
"screenshare": "unknown"
},
"lastSeenAt": 1750820602963
}
}
The same event is added to the participant's stored timeline with the same timestamp. For example, participant-muted is stored as {"type":"muted","timestamp":1750820602963}. See Participant Timelines for retrieval details.
stateis the current state after the event:activeis a boolean;microphoneismuted,unmuted, orunknown;cameraison,off, orunknown; andscreenshareissharing,not-sharing, orunknown.lastSeenAtis the epoch-millisecond time when the participant was last seen in the meeting.
Stop
The stop event indicates that recording has ended. No further transcript events will be sent, so the client can disconnect.
{
"type": "stop"
}
Error
The error event indicates that transcription has failed. No further transcript events will be sent.
{
"type": "error",
"data": {
"message": "Error message"
}
}
WebSocket Actions
You can also interact with the bot through WebSocket actions. Actions use the JSON structure below. WebSocket messages are strings, so serialize the JSON before sending it. Actions with missing required data are ignored.
{
"action": "[action]",
"data": {...}
}
Actions are only available through websocket_url. Messages sent through websocket_read_only_url are not executed.
Start Recording
For a bot created with recording_start_mode: "manual", send this action after your application has decided its participant and consent requirements are met:
{
"action": "start-recording"
}
Listen for the recording-started event or a status-update to recording before treating recording as active.
With the SDK, call realtimeClient.startRecording(). Repeated actions are safe: recording starts only once.
Send Chat Message
Use this action to send a message to the meeting chat.
Zoom only: include to to send a message to a specific recipient, or reply_to_message to reply inside a thread. Refer to the Zoom Chat Guide for important behavior notes regarding Meetings vs Webinars.
{
"action": "chat-message",
"data": {
"content": "Welcome to the meeting!",
"to": "Meeting Group Chat", // Zoom only
"reply_to_message": "msg_7c2b4a9" // Zoom only
}
}
Important behavior notes:
tois Zoom-only. To message everyone, use"Meeting Group Chat"for meetings and"Everyone"for webinars.- If the named Zoom recipient is not found, the bot will skip sending the message.
tois intended for non-threaded sends. Do not combinetoandreply_to_message.
Stop the bot
This action stops the bot.
{
"action": "stop"
}