Webhooks
Native Frame delivers events to your webhook endpoint. This page is the technical reference — the two webhook systems, payload formats, the response contract, and the full event catalog. For setup and how-to, see the prose guides:
- Webhooks Overview — the two systems and which you need
- Registering a Webhook — configure one in the dashboard
- Webhook Authentication — verifying deliveries
- Format Availability — the format events in depth
Two kinds of webhook
| Event notifications | Program-states integration | |
|---|---|---|
| Purpose | Tell you something happened | Ask your integration for a verdict |
| Direction | One-way (fire-and-forget) | Request → your response drives behavior |
| Delivery | Async, fanned out, retried (with history) | Synchronous, in the stream lifecycle path |
| Endpoints | Many — you register/manage URLs | Exactly one registered webhook |
| Your response | Ignored (only HTTP status matters) | stop / needAuth / appData are acted on |
Notification endpoints are managed in the dashboard or via the Events API (GET / PUT / DELETE /events/v2/webhook/{name}). The program-states integration is enabled by the private + program-states auth flow. See Webhooks Overview for the full comparison.
Delivery formats
Program-states events are delivered as a nested payload — the event field is program-states-v2 and payload holds the data. A flat shape also exists for direct integrations (see below).
Most integrations receive webhooks through the events service (the endpoint you register in the dashboard). On that path you receive only the JSON payload — there is no X-Project-ID header, since your registered endpoint already identifies the project. The X-Project-ID header, and the flat format below, are only available with direct integration against the auth service.
Nested — event: "program-states-v2" (standard)
State is nested program → stream → token, and each token carries the event that fired. tokens at the top level are users not yet associated with a program (e.g. requesting to create one).
{
"event": "program-states-v2",
"payload": {
"tokens": [],
"programs": {
"<programID>": {
"streams": {
"<streamID>": {
"producing": [
{
"streamName": "default",
"token": { "type": "token", "value": "<authKey>", "event": "starting" }
}
],
"consuming": [
{
"streamName": "default",
"token": { "type": "token", "value": "<viewerToken>", "event": "joining" }
}
]
}
}
}
}
}
}
Flat — event: "events" (direct integration only)
An alternative flat shape — a list of { id, type, payload } with no nesting (id is server-generated for response correlation). It's configured per project in the auth service and delivered only via direct integration; it is not available through the events-service webhook, and only a few integrations use it today. Most integrators use the nested shape above.
{
"event": "events",
"payload": {
"events": [
{ "id": "evt-1", "type": "producer-format-started", "payload": { "encodings": [ /* … */ ] } }
]
}
}
The response contract
For respondable events your reply drives platform behavior. The response is sparse — return only the tokens/levels you want to act on; anything omitted defaults to a no-op (stop=false, needAuth=false).
| Field | Available at | Effect |
|---|---|---|
stop (+ stopReason) | program, stream, token | Reject or tear down at that level |
needAuth | program, stream | Require (re)authentication |
appData | token | Opaque key/values passed back into the stream/token context |
{
"tokens": [],
"programs": {
"<programID>": {
"streams": {
"<streamID>": {
"producing": [
{ "streamName": "default",
"token": { "value": "<authKey>", "event": "starting", "stop": true, "stopReason": "not entitled" } }
]
}
}
}
}
}
Event catalog
Customer-facing events. Respondable = your response (stop/needAuth/appData) is acted on; Notification = informational only. In the nested shape the per-token event drops the scope prefix (e.g. producer-starting → starting).
| Event | Scope | Kind | Fires when |
|---|---|---|---|
token-joining | Token | Respondable | A user/token first connects (not yet tied to a program) — gate or attach appData |
token-hls-auth | Token | Respondable | An HLS playback authorization is requested |
token-auth-manifest | Token | Respondable | Program-level manifest access is authorized |
producer-starting | Producer | Respondable | A broadcast is starting — admission gate, return stop to reject before any session is created (fails closed) |
producer-creating | Producer | Respondable | A producing token is creating its stream on the gateway |
producer-polling | Producer | Respondable | Periodic verdict while a producer is live — return stop to tear it down |
consumer-joining | Consumer | Respondable | A viewer starts watching — gate or attach viewer appData |
consumer-polling | Consumer | Respondable | Periodic verdict while a viewer is connected |
program-state | Program | Respondable | Program-level state/verdict point |
producer-manifest-ready | Producer | Notification | The HLS/DASH manifest is ready |
producer-webrtc-manifest-ready | Producer | Notification | The WebRTC manifest is available |
producer-format-started | Producer | Notification | A transcode variant (encoding) became available — see Format Availability |
producer-format-ended | Producer | Notification | A transcode variant became unavailable |
producer-closed | Producer | Notification | The producer stopped streaming |
producer-disconnected | Producer | Notification | The producer transport/peer dropped |
consumer-closed | Consumer | Notification | A viewer stopped watching |
consumer-disconnected | Consumer | Notification | A viewer connection dropped |
token-disconnected | Token | Notification | A token left cleanly |
stream-state | Stream | Notification | Stream alias/metadata changed |
program-closed | Program | Notification | The program/stream ended |
Format availability (Format Begin / Format End)
producer-format-started (Format Begin) and producer-format-ended (Format End) fire as a transcode variant becomes available / unavailable. They can be delivered two ways — both carry the same payload, and the broadcast is identified by streamID:
- Program-states webhook — as
format-started/format-endedtoken events (theeventon the producing token). - Legacy callbacks —
PUT {host}/integration/v1/broadcast/{streamID}/encoding/{format}(begin) andDELETE …(end), where Native Frame calls an endpoint you host.
The Format Begin payload:
{
"videoCodec": "h264",
"audioCodec": "aac",
"manifest": "https://cdn.example.com/manifest.m3u8",
"location": "https://cdn.example.com/",
"clientEncoder": "obs",
"encodings": [
{
"isOriginal": true,
"videoWidth": 1280, "videoHeight": 720,
"videoKbps": 2500, "audioKbps": 128,
"videoPts": 12478, "audioPts": 12478,
"collected": 1718040000000,
"location": "https://cdn.example.com/720p/"
}
]
}
| Field | Notes |
|---|---|
videoCodec, audioCodec | e.g. h264, aac |
manifest | Viewer playback manifest URL |
location | Base media location |
clientEncoder | Broadcast encoder, e.g. obs |
encodings[] | One per variant: isOriginal, videoWidth/videoHeight, videoKbps/audioKbps, videoPts/audioPts, collected (unix millis), location |
Format End carries only the streamID — the encoding is identified by the callback path ({format}) or the surrounding token context.
See the Format Availability guide for both delivery methods, the response shape, and migration notes.