Webhooks
Inbound provider callbacks that advance a video's transcode status — Bunny, on-prem, and Cloudflare.
Streaming providers call these endpoints when a video finishes uploading or transcoding. They flip
the asset's stream_status (pending_upload → processing → ready / failed) and populate its
playback manifest.
You register these URLs with each provider — you don't call them yourself. They are not behind
the user auth guard; instead each is protected by the
provider's signature verification. Every handler returns 200 with a { processed } result, or
the standard error envelope on failure.
{
"success": true,
"message": "Webhook processed",
"code": 200,
"data": { "processed": true },
"request_id": "req_01J9Z0X8QE",
"timestamp": "2026-07-29T04:21:00.000Z"
}processed: true means an asset was matched and updated. processed: false means the payload was
valid but didn't match a known asset (or wasn't actionable) — the request still returns 200 so
the provider doesn't retry needlessly.
Bunny Stream
POST /api/v1/webhooks/stream/bunny → 200 OK
What it is — Bunny's callback reporting a video's upload/encoding status; it advances the
matching asset's stream_status and stores playback URLs once ready.
When to use it — You don't call it — you register the URL in Bunny. Bunny calls it as the video progresses.
How to use it — Register the endpoint in your Bunny library. Bunny signs each request with a v1 HMAC-SHA256 signature derived from your library Read-Only API key.
Scope — Public (no auth token); authenticated by the signature.
BUNNY_STREAM_READ_ONLY_API_KEY must be set. Requests without a valid v1 HMAC signature are
rejected with 401 UNAUTHORIZED — this signature check is mandatory, not optional.
Bunny's numeric Status maps to stream_status as follows:
Bunny Status | Meaning | stream_status |
|---|---|---|
| 3, 4 | Finished / playable | ready |
| 7 | TUS/upload finished, encoding continues | processing |
| 5, 8 | Error | failed |
curl -X POST https://media.example.com/api/v1/webhooks/stream/bunny \
-H "Content-Type: application/json" \
-d '{ "VideoLibraryId": 12345, "VideoGuid": "e7c2...", "Status": 3 }'On-prem encoder
POST /api/v1/webhooks/stream/onprem → 200 OK
What it is — Your own encoder's callback reporting transcode progress/completion, carrying the
asset_id, a status, and the generated playback URLs.
When to use it — Your encoder calls it after POST /media/{id}/complete triggers encoding, and
again when the video is ready or failed.
How to use it — Sign the raw body as sha256(rawBody + secret) in X-Onprem-Signature (or
X-Webhook-Signature). Playback URLs from the body are stored only when the status is ready.
Scope — Public (no auth token). Set ONPREM_WEBHOOK_SECRET to enforce the signature (if unset,
verification is skipped).
curl -X POST https://media.example.com/api/v1/webhooks/stream/onprem \
-H "Content-Type: application/json" \
-H "X-Signature: <hmac-sha256>" \
-d '{
"asset_id": "770e8400-e29b-41d4-a716-4466554400ff",
"status": "ready",
"playback": {
"hls": "https://media.example.com/api/v1/playback/770e8400.../hls.m3u8",
"dash": "https://media.example.com/api/v1/playback/770e8400.../dash.mpd",
"thumbnail": "https://media.example.com/api/v1/playback/770e8400.../thumb.jpg"
}
}'Cloudflare Stream
POST /api/v1/webhooks/stream/cloudflare → 200 OK
What it is — Cloudflare Stream's callback when a video finishes processing or errors; it advances
the matching asset's stream_status and stores playback once ready.
When to use it — You don't call it — register the URL once per account via
PUT /accounts/{account_id}/stream/webhook. Cloudflare calls it on completion.
How to use it — Cloudflare signs with HMAC-SHA256 over {time}.{rawBody} in the
Webhook-Signature: time=…,sig1=… header.
Scope — Public (no auth token). Set CLOUDFLARE_STREAM_WEBHOOK_SECRET to enforce the signature
(if unset, verification is skipped).
curl -X POST https://media.example.com/api/v1/webhooks/stream/cloudflare \
-H "Content-Type: application/json" \
-H "Webhook-Signature: time=1785000000,sig1=<hmac-sha256>" \
-d '{ "uid": "abc123", "status": { "state": "ready" } }'If a provider is not configured (its secret/key is unset) its webhook returns 401 "not configured". Invalid JSON returns 401 "Invalid webhook payload". An unrecognized but
well-formed payload returns 200 with { "processed": false }.