Skip to main content

Callback

When the video state changes, a JSON callback will be sent back to your URL as a POST request. Below is an example object that will be sent.
{
	"VideoLibraryId": 133,
	"VideoGuid": "657bb740-a71b-4529-a012-528021c31a92",
	"Status": 3
}

Status list

Below are the possible status codes sent with the webhook:
  • 0 - Queued: The video has been queued for encoding.
  • 1 - Processing: The video has begun processing the preview and format details.
  • 2 - Encoding: The video is encoding.
  • 3 - Finished: The video encoding has finished and the video is fully available.
  • 4 - Resolution finished: The encoder has finished processing one of the resolutions. The first request also signals that the video is now playable.
  • 5 - Failed: The video encoding failed. The video has finished processing.
  • 6 - PresignedUploadStarted : A pre-signed upload has been initiated.
  • 7 - PresignedUploadFinished : A pre-signed upload has been completed.
  • 8 - PresignedUploadFailed : A pre-signed upload has failed.
  • 9 - CaptionsGenerated : Automatic captions were generated.
  • 10 - TitleOrDescriptionGenerated : Automatic generation of title or description has been completed.

Signature validation

Verify that an incoming webhook request was sent by Bunny Stream and has not been tampered with.

Overview

Signed webhook POSTs use signature version v1. Each signed request includes the following headers:
HeaderValue
X-BunnyStream-Signature-Versionv1
X-BunnyStream-Signature-Algorithmhmac-sha256
X-BunnyStream-SignatureLowercase hex HMAC-SHA256 (64 characters)
The signature is an HMAC-SHA256 of the exact raw request body, using the webhook signing secret as the key, encoded as a lowercase hex string.

How the signature is built

version    = "v1"
algorithm  = "hmac-sha256"
signature  = lowercase_hex( HMAC-SHA256( utf8(body), utf8(signatureSecret) ) )
  • body: The exact raw HTTP request body bytes as received.
  • signatureSecret: Your library’s Read-Only API key, encoded as UTF-8.
  • Output: 64-character lowercase hexadecimal string.
  • The URL, timestamp, HTTP method, and headers are not part of the v1 signature.

Validation steps

1

Read the raw request body

Capture the raw body from the HTTP request stream before any parsing. Do not parse to JSON and re-serialize.
2

Check the version header

Ensure X-BunnyStream-Signature-Version is v1.
3

Check the algorithm header

Ensure X-BunnyStream-Signature-Algorithm is hmac-sha256.
4

Get your signing secret

Use your library’s Read-Only API key as the signing secret.
5

Compute the expected signature

expectedSignature = lowercase_hex(HMAC-SHA256(rawBody, signatureSecret))
6

Compare signatures

Read X-BunnyStream-Signature and compare it to your computed value using a constant-time comparison to avoid timing attacks. If they match, the request is authentic and the body was not modified in transit.

Code examples

const crypto = require('crypto');

function validateWebhookSignature(rawBody, signatureHeader, signatureVersion, signatureAlgorithm, signatureSecret) {
  if (signatureVersion !== 'v1') {
    return false;
  }

  if (signatureAlgorithm !== 'hmac-sha256') {
    return false;
  }

  const expectedHex = crypto
    .createHmac('sha256', signatureSecret)
    .update(rawBody, 'utf8')
    .digest('hex');

  if (
    typeof signatureHeader !== 'string' ||
    signatureHeader.length !== expectedHex.length ||
    !/^[0-9a-f]+$/.test(signatureHeader)
  ) {
    return false;
  }

  return crypto.timingSafeEqual(
    Buffer.from(expectedHex, 'utf8'),
    Buffer.from(signatureHeader, 'utf8')
  );
}

// Express: use body parser that keeps raw body
app.post('/webhook', express.raw({ type: 'application/json' }), (req, res) => {
  const signature = req.headers['x-bunnystream-signature'];
  const version = req.headers['x-bunnystream-signature-version'];
  const algorithm = req.headers['x-bunnystream-signature-algorithm'];
  const rawBody = req.body.toString('utf8');
  if (!validateWebhookSignature(rawBody, signature, version, algorithm, process.env.READ_ONLY_API_KEY)) {
    return res.status(401).send('Invalid signature');
  }
  const data = JSON.parse(rawBody);
  // ... handle webhook
});

Important notes

Always use the exact raw body bytes for validation. If your framework parses JSON and re-serializes it, whitespace or key ordering may change and the signature will not match.
  • Constant-time comparison: Use timingSafeEqual, hmac.compare_digest, or CryptographicOperations.FixedTimeEquals so comparison time does not leak information about the secret.
  • Signing secret: Use your library’s Read-Only API key as the signing secret. Keep it server-side only; never expose it to the client.
  • Version & algorithm headers: Always validate both X-BunnyStream-Signature-Version and X-BunnyStream-Signature-Algorithm before computing the signature.