# 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.

```json
{
  "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.

## Payload

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

```json
{
  "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](/api-reference/v2/live/websocket/#receive). `pii` and `end_of_turn` messages
are not sent to callbacks.

## Verify the signature

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).

```python
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)
```

```js
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.

## Delivery

- 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](/live/reconnections/), messages of the old and new connections may interleave: order them
  by the utterance `id`.

Answer `2xx` quickly and process the message afterwards.
