Skip to main content

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:

Two kinds of webhook

Event notificationsProgram-states integration
PurposeTell you something happenedAsk your integration for a verdict
DirectionOne-way (fire-and-forget)Request → your response drives behavior
DeliveryAsync, fanned out, retried (with history)Synchronous, in the stream lifecycle path
EndpointsMany — you register/manage URLsExactly one registered webhook
Your responseIgnored (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).

How deliveries reach you

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

FieldAvailable atEffect
stop (+ stopReason)program, stream, tokenReject or tear down at that level
needAuthprogram, streamRequire (re)authentication
appDatatokenOpaque 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-startingstarting).

EventScopeKindFires when
token-joiningTokenRespondableA user/token first connects (not yet tied to a program) — gate or attach appData
token-hls-authTokenRespondableAn HLS playback authorization is requested
token-auth-manifestTokenRespondableProgram-level manifest access is authorized
producer-startingProducerRespondableA broadcast is starting — admission gate, return stop to reject before any session is created (fails closed)
producer-creatingProducerRespondableA producing token is creating its stream on the gateway
producer-pollingProducerRespondablePeriodic verdict while a producer is live — return stop to tear it down
consumer-joiningConsumerRespondableA viewer starts watching — gate or attach viewer appData
consumer-pollingConsumerRespondablePeriodic verdict while a viewer is connected
program-stateProgramRespondableProgram-level state/verdict point
producer-manifest-readyProducerNotificationThe HLS/DASH manifest is ready
producer-webrtc-manifest-readyProducerNotificationThe WebRTC manifest is available
producer-format-startedProducerNotificationA transcode variant (encoding) became available — see Format Availability
producer-format-endedProducerNotificationA transcode variant became unavailable
producer-closedProducerNotificationThe producer stopped streaming
producer-disconnectedProducerNotificationThe producer transport/peer dropped
consumer-closedConsumerNotificationA viewer stopped watching
consumer-disconnectedConsumerNotificationA viewer connection dropped
token-disconnectedTokenNotificationA token left cleanly
stream-stateStreamNotificationStream alias/metadata changed
program-closedProgramNotificationThe 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-ended token events (the event on the producing token).
  • Legacy callbacksPUT {host}/integration/v1/broadcast/{streamID}/encoding/{format} (begin) and DELETE … (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/"
}
]
}
FieldNotes
videoCodec, audioCodece.g. h264, aac
manifestViewer playback manifest URL
locationBase media location
clientEncoderBroadcast 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.

See also