Webhooks Overview
Native Frame uses webhooks in two different ways, and it's worth knowing which is which because they behave very differently.
| Event notifications | Program-states integration | |
|---|---|---|
| Purpose | Tell you something happened | Ask your integration for a verdict |
| Direction | One-way (fire-and-forget) | Request/response — your reply drives platform behavior |
| Delivery | Async, fanned out, retried (exponential backoff), with delivery history | Synchronous, in the stream lifecycle path |
| Webhooks registered | Many — you register/manage URLs | Exactly one registered webhook |
| What your response does | Nothing (only the HTTP status matters) | stop / needAuth / appData can veto a start, tear down a stream, or reject a peer |
1. Event notifications
You register one or more webhook endpoints and Native Frame delivers events to them as things happen — asynchronously, with retries and full delivery history. Your endpoint just needs to accept the delivery (the response body isn't acted on). Registration and management are done through the dashboard, or via the Events API (GET / PUT / DELETE /events/v2/webhook/{name}). See Webhooks to set one up.
2. Program-states integration
This is the verdict path used for stream-lifecycle decisions and lifecycle notifications. Native Frame calls your single registered webhook synchronously; for some events your response decides what happens next.
Events fall into two kinds.
Respondable (verdict) events — your response can set stop, needAuth, appData:
| Event | What it's for |
|---|---|
starting | Admission gate. Fired synchronously on start-stream/start-broadcast. Return stop (at program, stream, or token level) to reject the broadcast before any session is created. Fails closed — if the webhook call itself fails, the start is blocked. |
polling | Ongoing verdict while a token is connected — return stop to tear the token/stream down. |
| token / consumer join + poll events | Per-participant admission/verdict. |
Notification events — informational, no response needed:
| Event | Fired when |
|---|---|
format-started / format-ended | A transcode variant becomes available / unavailable — see the Format Availability guide. |
closed | Fire-and-forget on end-stream/end-broadcast for each active stream. |
manifest-ready, disconnected, etc. | Various lifecycle notifications. |
Delivery shape
Program-states events are delivered nested — programs → streams → producing/consuming → token.event. On the standard path (the webhook you register in the dashboard) the JSON body is the payload itself: programs and tokens sit at the top level, with no event/id wrapper around them.
The response is a full mirror, not a delta: echo back every program, stream, producing/consuming entry, and token you received, setting the verdict fields (stop, needAuth, appData) on each. Anything you leave out is treated as stop: true — an empty response stops everything in the request, including your own broadcaster. See the Webhooks API reference for the exact shapes.
events format — not yet available via webhooksThe platform also defines a flat, event-based payload (event: "events"), 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. Integrate against the nested program-states-v2 shape.
Playback admission — hls-auth
The events above are the producer side. Viewer playback on a private stream is admitted through the same webhook, by a different event, in a different shape.
When a viewer requests the manifest of a private stream with an access token, Native Frame calls your endpoint with an hls-auth action carried under viewTokens — the token / viewTokens shape, not the producing / consuming tree described above. A handler that branches only on the program-states-v2 shape never matches it, and the fail-closed default leaves the viewer unauthorized.
Two rules decide whether you are called at all:
- Token format. A manifest access token must match
^[0-9A-Za-z_+/=-]+$. A token that fails the check — a JWT (contains.) or an email address — is rejected before the webhook fires. Your endpoint sees nothing, which reads as a delivery failure but is a format failure. - The
view-privaterole. A viewer JWT carrying a top-levelroles: ["view-private"]claim is honored directly, and nohls-authwebhook fires. This is distinct fromvideoToken.scopes: ["private-viewer"], which governs WebRTCjoining, not HLS playback.
An unauthorized viewer is not refused: they are served a blurred low-resolution substitute rendition at HTTP 200. Both paths, and that failure mode, are in Private Stream Viewer Auth.
Which one do I need?
- "Tell me when X happens" (recordings ready, analytics, generic notifications) → register an event-notification webhook.
- "Let me approve/deny streams, or know about live stream state and formats" → you're on the program-states integration (private + program-states auth flow). Format availability, the start admission gate, and polling verdicts all arrive here.
- "Let me decide who can watch a private stream" → also the program-states integration, via
hls-auth— or skip the webhook entirely by minting viewer JWTs with theview-privaterole. See Private Stream Viewer Auth.
For the payload formats, the response contract (stop / needAuth / appData), and the full event catalog, see the Webhooks API reference.