Stay in sync with signed webhooks
Set a callback URL and StreamHub POSTs a signed envelope on every stream and VOD event. How to verify the HMAC signature and handle retries.
Push, do not poll
Instead of polling the API to find out when a stream started or a recording finished, give StreamHub a callback URL. On every lifecycle event it POSTs a signed JSON envelope to your backend — stream_started, stream_ended, vod_ready, recording_failed, and more.
Configure the callback
Set callbacks.url (and ideally callbacks.secret) on the app's config. From then on, each event delivers an envelope:
{
"id": "f2b1c8e4-...",
"event": "vod_ready",
"app": "live",
"timestamp": "2026-06-30T12:00:00.000Z",
"data": { /* event-specific payload */ }
}
Alongside it come headers you can key off: X-StreamHub-Event, X-StreamHub-Delivery (the unique id), X-StreamHub-Timestamp, and — when a secret is set — X-StreamHub-Signature.
Verify the signature
The signature is sha256= followed by the HMAC-SHA256 of the exact received body with your shared secret. Always compare using a timing-safe equality:
const crypto = require('crypto');
function verify(req, secret) {
const expected = 'sha256=' + crypto
.createHmac('sha256', secret)
.update(req.rawBody) // the exact bytes received
.digest('hex');
return crypto.timingSafeEqual(
Buffer.from(expected),
Buffer.from(req.header('X-StreamHub-Signature')),
);
}
Compute the HMAC over the raw request bytes, not a re-serialized object — re-serialization can change whitespace and break the match.
Handle retries idempotently
Delivery is at-least-once. StreamHub retries up to 3 times with exponential backoff (500ms, 1s) and a 10s timeout. It retries 5xx, 408 and 429; other 4xx are treated as a rejection and not retried. Respond 2xx to acknowledge.
Because a delivery can arrive more than once, dedupe on X-StreamHub-Delivery (the UUID) and make your handler idempotent. Store the delivery id; if you have seen it, ack and move on.
What you can build
With signed callbacks you can update a "live now" badge the instant a publisher connects, kick off post-processing when vod_ready arrives, page an on-call engineer on recording_failed, or write your own analytics — all without a single polling loop. And because callbacks never throw back into the streaming flow, a flaky endpoint on your side never disrupts the media pipeline.