API REFERENCE · PLATFORM
Webhooks
Every state change lands on your endpoint, signed. Set a default endpoint at onboarding, or override it per dub or per session with webhook_url.
01.The event catalog
| EVENT | FIRES WHEN |
|---|---|
dub.ready_for_review | A dub reached in_review (a review=manual dub, or any subtitle job) and is waiting on your edit + render. |
dub.output_ready | One language finished rendering; its output is downloadable. |
dub.completed | Every language of the dub is rendered. |
dub.failed | The dub (or one of its languages) failed; the payload carries the error. |
live.session_ready | A session finished provisioning; the ingest will accept media. |
live.session_started | A live session's source stream arrived; dubbed feeds are pushing. |
live.session_ended | The source stopped or the session was ended. |
New event types may be added within /v1-- ignore types you don't recognize (Versioning & stability).
02.Payload schemas
Every delivery is the same envelope; data varies by type and always carries the IDs you need to act.
{
"id": "evt_92ms1t",
"type": "dub.output_ready",
"created_at": "2026-08-13T17:41:03Z",
"data": { … }
}| FIELD | TYPE | DESCRIPTION |
|---|---|---|
id | string | Unique per event; the dedupe key on redelivery. |
type | string | One of the catalog above. |
created_at | string | ISO 8601, UTC -- when the event happened, not when it was delivered. |
data | object | The event body; see the examples below. |
{
"dub_id": "dub_31xk9m",
"langs": ["es", "pt", "ja"],
"transcript_url": "https://api.thefamiliarlab.com/v1/dubs/dub_31xk9m/transcript"
}{
"dub_id": "dub_31xk9m",
"lang": "es",
"output_url": "https://api.thefamiliarlab.com/v1/dubs/dub_31xk9m/output/es"
}{
"dub_id": "dub_31xk9m",
"langs": ["es", "pt", "ja"]
}{
"dub_id": "dub_31xk9m",
"lang": "ja",
"error": {
"type": "internal",
"message": "Rendering failed; the job may be retried.",
"doc_url": "https://thefamiliarlab.com/docs/errors"
}
}{
"session_id": "live_77aq2c",
"creator_id": "cr_8f2k1q",
"target_langs": ["es", "ko"]
}{
"session_id": "live_77aq2c",
"creator_id": "cr_8f2k1q",
"target_langs": ["es", "ko"],
"started_at": "2026-08-13T18:04:02Z"
}{
"session_id": "live_77aq2c",
"creator_id": "cr_8f2k1q",
"ended_at": "2026-08-13T20:19:47Z",
"reason": "source_stopped"
}reason on live.session_ended is source_stopped (the grace window lapsed) or deleted (you ended it).
03.Delivery & retries
- Respond with any
2xxwithin 10 seconds -- do the real work after acknowledging, not before. - Anything else is retried with exponential backoff for 24 hours, then dropped -- poll the dub as the fallback of record.
- Delivery order isn't guaranteed and duplicates happen -- dedupe on the event
idand treat the resource'sGETas the source of truth.
04.Verify the signature
Every delivery carries a Familiar-Signature header: a timestamp and an HMAC-SHA256 of the raw body, keyed with your webhook secret (issued with your API key).
Familiar-Signature: t=1786731663,v1=5f8c1e… signed_payload = t + "." + raw_body expected = HMAC_SHA256(webhook_secret, signed_payload)
import { createHmac, timingSafeEqual } from "node:crypto";
function verify(rawBody, header, secret) {
const parts = Object.fromEntries(
header.split(",").map((kv) => kv.split("=")),
);
const ageSec = Math.abs(Date.now() / 1000 - Number(parts.t));
if (ageSec > 300) return false; // closes replay
const expected = createHmac("sha256", secret)
.update(parts.t + "." + rawBody)
.digest("hex");
return timingSafeEqual(Buffer.from(expected), Buffer.from(parts.v1));
}- Compare with a constant-time comparison, never
===. - Verify against the raw request body, before any JSON parsing re-serializes it.
- Reject deliveries whose
tis older than ~5 minutes -- that closes replay.
05.Endpoint configuration
| LEVEL | SET BY | WINS WHEN |
|---|---|---|
| Organization default | At onboarding, with the team. | Nothing narrower is set. |
| Per dub | webhook_url on POST /v1/dubs. | For that dub's events. |
| Per session | webhook_url on POST /v1/live/sessions. | For that session's events. |
Endpoints must be HTTPS. One secret signs every delivery, whichever level routed it.
06.Access
The API is enterprise-only: private access under a Studio contract, with volume pricing, raised limits, and a named point of contact for the integration. Book a call and your key is issued with the contract.