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

TABLE 01 · EVENT TYPES
EVENTFIRES WHEN
dub.ready_for_reviewA dub reached in_review (a review=manual dub, or any subtitle job) and is waiting on your edit + render.
dub.output_readyOne language finished rendering; its output is downloadable.
dub.completedEvery language of the dub is rendered.
dub.failedThe dub (or one of its languages) failed; the payload carries the error.
live.session_readyA session finished provisioning; the ingest will accept media.
live.session_startedA live session's source stream arrived; dubbed feeds are pushing.
live.session_endedThe 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.

ENVELOPE
{
  "id": "evt_92ms1t",
  "type": "dub.output_ready",
  "created_at": "2026-08-13T17:41:03Z",
  "data": { … }
}
TABLE 02 · ENVELOPE FIELDS
FIELDTYPEDESCRIPTION
idstringUnique per event; the dedupe key on redelivery.
typestringOne of the catalog above.
created_atstringISO 8601, UTC -- when the event happened, not when it was delivered.
dataobjectThe event body; see the examples below.
dub.ready_for_review · DATA
{
  "dub_id": "dub_31xk9m",
  "langs": ["es", "pt", "ja"],
  "transcript_url": "https://api.thefamiliarlab.com/v1/dubs/dub_31xk9m/transcript"
}
dub.output_ready · DATA
{
  "dub_id": "dub_31xk9m",
  "lang": "es",
  "output_url": "https://api.thefamiliarlab.com/v1/dubs/dub_31xk9m/output/es"
}
dub.completed · DATA
{
  "dub_id": "dub_31xk9m",
  "langs": ["es", "pt", "ja"]
}
dub.failed · DATA
{
  "dub_id": "dub_31xk9m",
  "lang": "ja",
  "error": {
    "type": "internal",
    "message": "Rendering failed; the job may be retried.",
    "doc_url": "https://thefamiliarlab.com/docs/errors"
  }
}
live.session_ready · DATA
{
  "session_id": "live_77aq2c",
  "creator_id": "cr_8f2k1q",
  "target_langs": ["es", "ko"]
}
live.session_started · DATA
{
  "session_id": "live_77aq2c",
  "creator_id": "cr_8f2k1q",
  "target_langs": ["es", "ko"],
  "started_at": "2026-08-13T18:04:02Z"
}
live.session_ended · DATA
{
  "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 2xx within 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 id and treat the resource's GET as 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).

THE SCHEME
Familiar-Signature: t=1786731663,v1=5f8c1e…

signed_payload = t + "." + raw_body
expected       = HMAC_SHA256(webhook_secret, signed_payload)
NODE.JS · VERIFY
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 t is older than ~5 minutes -- that closes replay.

05.Endpoint configuration

TABLE 03 · WHERE EVENTS GO
LEVELSET BYWINS WHEN
Organization defaultAt onboarding, with the team.Nothing narrower is set.
Per dubwebhook_url on POST /v1/dubs.For that dub's events.
Per sessionwebhook_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.