Guide to Bunny Stream Webhooks: Features and Best Practices
Bunny Stream webhooks notify your systems about video processing activity: a video finishes encoding, fails, or has captions generated. If you're building on Bunny Stream, webhooks are how you react to video status changes without polling.
This guide covers how Bunny Stream webhooks work, the Status enum, how to verify the X-BunnyStream-Signature, and the best practices for production.
What are Bunny Stream webhooks?
Bunny Stream webhooks are HTTP POSTs delivered to a URL you configure per video library, each carrying a thin payload about a video's status. Bunny signs deliveries with an X-BunnyStream-Signature header: an HMAC-SHA256 of the raw body, keyed with the library's Read-Only API key, lowercase hex.
The payload is thin (VideoLibraryId, VideoGuid, Status), so you fetch full metadata via the Stream API using the VideoGuid.
Bunny Stream webhook features
| Feature | Details |
|---|---|
| Configuration | Per video library in the Stream dashboard |
| Signature header | X-BunnyStream-Signature (lowercase hex) |
| Signature scheme | HMAC-SHA256 over the raw body |
| Key | The video library's Read-Only API key |
| Payload | Thin: VideoLibraryId, VideoGuid, Status |
| Retries | Not documented |
| SDK | None (community only) |
The Status enum
The payload's Status is an integer. The full enum:
| Status | Meaning |
|---|---|
| 0 | Queued |
| 1 | Processing |
| 2 | Encoding |
| 3 | Finished (encoding done) |
| 4 | ResolutionFinished |
| 5 | Failed (error) |
| 6 | PresignedUploadStarted |
| 7 | PresignedUploadFinished |
| 8 | PresignedUploadFailed |
| 9 | CaptionsGenerated |
| 10 | TitleOrDescriptionGenerated |
Encoding-done is 3 (Finished) and the error state is 5 (Failed). Branch on the integer Status.
Setting up Bunny Stream webhooks
Configure the webhook URL per video library in the Stream dashboard. The signing secret is that library's Read-Only API key, use it as the HMAC key.
Securing Bunny Stream webhooks
The X-BunnyStream-Signature header is a lowercase-hex HMAC-SHA256 of the raw request body, keyed with the library's Read-Only API key. Compute over the exact raw bytes and compare in constant time. (Bunny signs the raw body, not a field concatenation.)
const crypto = require("crypto");
const READ_ONLY_KEY = process.env.BUNNY_STREAM_READONLY_KEY; // library Read-Only API key
function verify(rawBody, signature) {
const expected = crypto
.createHmac("sha256", READ_ONLY_KEY)
.update(rawBody)
.digest("hex");
const a = Buffer.from(expected);
const b = Buffer.from(signature || "");
return a.length === b.length && crypto.timingSafeEqual(a, b);
}
app.post("/webhook", express.raw({ type: "application/json" }), (req, res) => {
if (!verify(req.body, req.headers["x-bunnystream-signature"])) {
return res.sendStatus(401);
}
res.sendStatus(200); // acknowledge fast
const { VideoGuid, Status } = JSON.parse(req.body);
processQueue.add({ VideoGuid, Status }); // fetch metadata via API, async
});
The same check in Python:
import hashlib
import hmac
import os
READ_ONLY_KEY = os.environ["BUNNY_STREAM_READONLY_KEY"].encode()
def verify(raw_body: bytes, signature: str) -> bool:
expected = hmac.new(READ_ONLY_KEY, raw_body, hashlib.sha256).hexdigest()
return hmac.compare_digest(expected, signature or "")
Bunny Stream webhook limitations and pain points
The secret is the library's Read-Only API key
The Problem: There's no dedicated webhook secret, the HMAC key is the library's Read-Only API key. Using the wrong key (or another library's) fails verification.
Why It Happens: Bunny keys webhook signing on the library Read-Only API key.
Workarounds:
- Use the correct library's Read-Only API key as the HMAC key.
How Hookdeck Can Help: Hookdeck verifies the X-BunnyStream-Signature at the edge, so the key lives in one place.
Don't confuse it with Bunny's other webhook scheme
The Problem: A different Bunny product documents an HMAC-SHA1 x-bunny-signature scheme for general (non-Stream) webhooks. Applying that to Stream (which is SHA256, X-BunnyStream-Signature) fails.
Why It Happens: Bunny has two webhook products with different schemes and headers.
Workarounds:
- For Stream, use HMAC-SHA256 and the
X-BunnyStream-Signatureheader.
How Hookdeck Can Help: Hookdeck verifies the Stream scheme at the edge, so your app isn't coupled to which Bunny product it's from.
Thin payload requires an API fetch
The Problem: The payload carries only VideoLibraryId, VideoGuid, and Status, so you fetch full metadata via the Stream API for anything useful.
Why It Happens: Bunny keeps the payload minimal.
Workarounds:
- Acknowledge fast, then fetch metadata via the API using
VideoGuidoff a queue.
How Hookdeck Can Help: Hookdeck durably queues each event so your worker fetches metadata at a controlled rate.
Undocumented retries
The Problem: Retry behavior isn't documented, so you can't assume a failed delivery will be retried.
Why It Happens: Bunny doesn't publish the retry policy.
Workarounds:
- Persist events on receipt and reconcile via the API; dedupe on
VideoGuid+Status.
How Hookdeck Can Help: Hookdeck durably stores and retries deliveries on a schedule you control. See our guide to webhook idempotency.
Best practices
Verify with the Read-Only API key over the raw body
Compute lowercase-hex HMAC-SHA256 over the raw body with the library's Read-Only API key and compare in constant time.
Branch on the integer Status
Use 3 (Finished) for encoding-done and 5 (Failed) for errors; branch on the integer.
Acknowledge fast, fetch via the API
Return 200 immediately and fetch metadata via the Stream API using VideoGuid off a queue. See why to process webhooks asynchronously.
Dedupe and reconcile
Dedupe on VideoGuid + Status, and reconcile via the API since retries aren't documented.
Make Bunny Stream webhooks production-ready
Hookdeck verifies X-BunnyStream-Signature, deduplicates, and durably queues every video event
Conclusion
Bunny Stream webhooks verify with an X-BunnyStream-Signature HMAC-SHA256 over the raw body, keyed with the library's Read-Only API key, and carry a thin VideoGuid/Status payload (3 = Finished, 5 = Failed). Verify against the raw body, branch on the integer Status, fetch metadata via the API, and don't confuse it with Bunny's SHA1 x-bunny-signature product.
Hookdeck verifies the signature, deduplicates, and durably queues every event at the edge, so your app only ever processes verified, unique video events.
Get started with Hookdeck for free and handle Bunny Stream webhooks reliably in minutes.