Skip to content

Callbacks

With callbacks, the API also POSTs a session’s messages to your server: to store transcripts as they come, or process them away from the client that streams the audio.

{
"callback": true,
"callback_config": {
"url": "https://hooks.example.com/gladia",
"receive_final_transcripts": true,
"receive_post_processing_events": true,
"receive_lifecycle_events": true
}
}

callback_config chooses the messages independently of the WebSocket’s messages_config; its defaults differ (final transcripts, post-processing and lifecycle events on, the rest off). The URL must be https, on a public address.

Each POST carries the session id, the event and the WebSocket message:

{
"id": "0199d7a2-5c3e-7b41-9f0a-3c2d1e4f5a6b",
"event": "live.transcript",
"payload": { "type": "transcript", "session_id": "0199d7a2-...", "data": { "id": "00_00000003", "is_final": true, "utterance": { "text": "..." } } }
}

event is live. followed by the message type: live.start_session (callbacks only, when the WebSocket opens), live.start_recording, live.transcript, live.speech_start, live.speech_end, live.translation, live.named_entity_recognition, live.sentiment_analysis, live.audio_chunk, live.stop_recording, live.end_recording, live.post_transcript, live.post_final_transcript, live.end_session, live.error. The payloads are the WebSocket messages. pii and end_of_turn messages are not sent to callbacks.

Each POST is signed with your workspace’s callback signing secret, in the header:

x-gladia-signature: t=<unix seconds>,v1=<hex>

v1 is the hex HMAC-SHA256, keyed with the secret, of <t>. followed by the raw request body (the bytes as received, before any JSON parsing). To verify a callback:

  1. Split the header on , and read t and v1.
  2. Compute HMAC-SHA256(secret, "<t>." + raw body) and compare it with v1 in constant time.
  3. Refuse a t older than your replay window (5 minutes is a good default).
import hashlib, hmac, time
def verify(secret: str, header: str, raw_body: bytes, window_s: int = 300) -> bool:
parts = dict(p.split("=", 1) for p in header.split(","))
t, v1 = parts.get("t", ""), parts.get("v1", "")
if not t.isdigit() or abs(time.time() - int(t)) > window_s:
return False
expected = hmac.new(secret.encode(), f"{t}.".encode() + raw_body, hashlib.sha256).hexdigest()
return hmac.compare_digest(expected, v1)
import { createHmac, timingSafeEqual } from "node:crypto";
export function verify(secret, header, rawBody, windowS = 300) {
const parts = Object.fromEntries(header.split(",").map((p) => p.split("=", 2)));
if (!/^\d+$/.test(parts.t ?? "") || Math.abs(Date.now() / 1000 - Number(parts.t)) > windowS) return false;
const expected = createHmac("sha256", secret).update(`${parts.t}.`).update(rawBody).digest("hex");
return expected.length === parts.v1?.length && timingSafeEqual(Buffer.from(expected), Buffer.from(parts.v1));
}

The secret (whsec_…) is created at your workspace’s first callback. Owners and admins rotate it in Gladia Workspace, Settings → Security → Callback signing secret: the new secret is shown once, and the previous one stops signing at once.

  • Messages are sent in order, one at a time per connection; a slow or failing receiver never slows the session.
  • Network errors and 5xx answers are retried, 3 attempts in all with a growing delay; 4xx answers are not.
  • Each POST times out after 5 seconds.
  • After a session ends, its remaining messages are delivered for up to 30 seconds.
  • Up to 1,024 messages wait per connection; beyond, messages are dropped.
  • After a reconnection, messages of the old and new connections may interleave: order them by the utterance id.

Answer 2xx quickly and process the message afterwards.