Webhooks and a Public Event Catalog
Until now, a FrameWorks integration that needed to react to a stream going live or a clip becoming ready had two options: keep a GraphQL subscription open, or poll. Neither works well for a serverless function or a backend that restarts during deploys. Webhooks are the missing piece, and before we could send them we needed a set of events that is safe to hand to a third-party server and stable enough to write code against.
Both now exist. Your account’s events are published as a versioned catalog, available live over a subscription and as signed HTTPS requests to endpoints you register.
An event is a promise about something that already happened
Section titled “An event is a promise about something that already happened”The event catalog covers streams going live or idle, stream keys being rotated, clips, recordings and uploads becoming ready or failing, multistream target status, API tokens, billing, account suspension, and custom domains. Each type has a protobuf message in a versioned public package, and that message is the payload.
Two rules make the catalog safe to build on. First, an event is written in the same database
transaction as the change it describes. A billing.invoice_paid event exists only if the invoice
really is paid; there is no window where your backend hears about a change that later rolled back.
Second, a payload carries only identifiers and values your account already sees through the API.
It does not carry internal stream names, node IDs, storage paths, IP addresses, or raw error text;
a failed clip reports a coarse reason rather than a stack trace.
Within the v1 package fields are only added. A breaking change ships as a v2 message, and each
webhook endpoint stays on the version it was created with, so a receiver you wrote today keeps
parsing what it receives.
The same events are available over the tenantEvents GraphQL subscription, filtered by type and
stream. The dashboard’s media library uses it to refresh when an artifact finishes processing.
What a delivery looks like
Section titled “What a delivery looks like”Under Developer → Webhooks you add HTTPS endpoints and pick the event types each one receives.
Each event becomes a POST:
webhook-id: 0199a3c2-7e41-7d2a-9b3e-5f8c1d2e4a67webhook-timestamp: 1789826608webhook-signature: v1,NSZG8yLlem8faHh7EcOnDIbFIp4Wa/3hhM320Atl5LM=
{"id":"0199a3c2-7e41-7d2a-9b3e-5f8c1d2e4a67","type":"clip.ready","api_version":"v1","created_at":"2026-09-19T14:03:27.412Z","data":{"artifact":{"artifactId":"c7f3a9e2b41d4f6a8e0b5d2c9a1f7e34","kind":"ARTIFACT_KIND_CLIP","streamId":"5b0e6a2c-3f1d-4c8e-9a7b-2d4f6e8a0c13","playbackId":"k3v9x2m7q4w8"},"durationMs":"30000","sizeBytes":"7340032"}}That is a real request body and signature, produced by the delivery code with the example secret
whsec_MfKQ9r8GKYqrTwjUPD8ILPZIo2LaLaSw. The data object uses the protobuf JSON mapping, so
64-bit values such as sizeBytes are strings.
Why we did not invent a signature scheme
Section titled “Why we did not invent a signature scheme”The signature follows the Standard Webhooks specification:
HMAC-SHA256 over <webhook-id>.<webhook-timestamp>.<body>. We chose it over a scheme of our own
because verification libraries already exist for most languages. In Node:
import { Webhook } from "standardwebhooks";
const event = new Webhook(process.env.FRAMEWORKS_WEBHOOK_SECRET!).verify(rawBody, headers);We checked the example above against the official TypeScript, Go, and Python libraries; all three accept it. The timestamp is part of the signed content, so a captured request cannot be replayed against you days later.
The webhook-id is the event ID, and it stays the same across every attempt, every replay, and
every endpoint. That is the one field a receiver needs to handle duplicates: store the IDs you have
processed and ignore a repeat. Failed deliveries are retried for about three days, every attempt is
logged for 30 days, and you can replay one delivery or a whole time range after an outage on your
side; the webhooks guide has the schedule and the replay calls.
A slow receiver only delays itself
Section titled “A slow receiver only delays itself”Delivery runs apart from the systems that validate stream keys and serve playback. A customer endpoint that hangs or returns errors delays its own deliveries and nobody else’s.
Endpoint URLs must resolve to public addresses, and that check runs again on every connection. A DNS record changed to point at an internal address after the endpoint was created is refused at send time, so a webhook cannot be turned into a way to reach our private network.
The limit worth knowing up front: an endpoint receives events from the moment it is created, with no backfill, and events carry no ordering guarantee across retries. Design the receiver to look up current state when order matters.
The webhooks guide covers endpoints, verification in TypeScript, Go, and Python, retries, auto-disable, secret rotation, and replay. The events reference lists every event type, when it fires, and its payload fields. Agents connected over MCP can list, test, and replay deliveries with an explicit confirmation.