← Back to the blog
Guides #webhooks

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.

2 min read
Leer en español

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.