Docs › Webhooks
Webhooks
Get notified when videos finish or fail, and when you're approaching your plan's limits. Set up a webhook endpoint (URL) in the web app on your Account → Webhooks page, or with the API:
GET /webhook— your current configuration.PUT /webhook {url, events?, enabled?}— save or update an endpoint.DELETE /webhook— remove the endpoint.POST /webhook/rotate— generate a new signing secret (keep the URL).POST /webhook/test— send a test event and report the result immediately.GET /webhook/deliveries— the last 30 days of delivery attempts.
Events
Each account has one endpoint that receives events for all types it's subscribed to. The possible events are:
| Event | Sent when | Data |
|---|---|---|
video.ready | Processing succeeded. | videoId, fileName, statusPath, metadata (if set), durationSeconds, downloadUrl (valid 1 hour), downloadExpiresInSeconds, posterUrl, playbackUrl, posterPlaybackUrl and playbackExpiresAt (locked to your allowed domains), streamUrl and streamExpiresAt if streaming, captions if made with captions, and status. |
video.failed | Processing failed or timed out. | videoId, fileName, statusPath, metadata, status: FAILED or TIMED_OUT. |
video.canceled | Processing was aborted. | videoId, fileName, statusPath, metadata. |
video.deleted | A video was deleted. | videoId, fileName, statusPath, metadata. |
usage.threshold | 80% or 100% of your plan's monthly limits is reached (once each per month). | measure (minutes, storage or delivery), percent (80 or 100), used, included (plan's allowance), month (YYYY-MM), plan (your plan name), overage (whether you'll be billed for excess). |
Webhook envelope
Every event arrives as JSON with metadata about the event:
{
"id": "evt_a1b2c3d4e5f6g7h8",
"type": "video.ready",
"createdAt": "2025-01-15T14:30:45Z",
"data": { event-specific data }
}
Signature verification (Standard Webhooks)
Each delivery includes headers webhook-id, webhook-timestamp and webhook-signature that implement the Standard Webhooks specification, so libraries for any language can verify them:
// npm install standardwebhooks
import { Webhook } from "standardwebhooks";
const wh = new Webhook(webhookSecret); // your secret from the account page
try {
const payload = wh.verify(body, {
"webhook-id": req.headers["webhook-id"],
"webhook-timestamp": req.headers["webhook-timestamp"],
"webhook-signature": req.headers["webhook-signature"],
});
// payload is the parsed event; proceed
} catch (err) {
res.status(401).send("Unauthorized");
}
# pip install svix
from svix.webhooks import Webhook
wh = Webhook(webhook_secret) # your secret from the account page
try:
payload = wh.verify(body, {
"webhook-id": headers["webhook-id"],
"webhook-timestamp": headers["webhook-timestamp"],
"webhook-signature": headers["webhook-signature"],
})
# payload is the parsed event; proceed
except Exception as e:
return {"error": "Unauthorized"}, 401
// Manual verification (no library needed)
import crypto from "crypto";
const id = req.headers["webhook-id"];
const timestamp = req.headers["webhook-timestamp"];
const signature = req.headers["webhook-signature"];
const body = /* raw request body as string */;
// Extract the base64 part of the secret (after "whsec_")
const secretBytes = Buffer.from(webhookSecret.slice(6), "base64");
// Sign: HMAC-SHA256 of "id.timestamp.body"
const signed = crypto
.createHmac("sha256", secretBytes)
.update(`${id}.${timestamp}.${body}`)
.digest("base64");
// The header is "v1,<signature>"
const expected = `v1,${signed}`;
if (signature !== expected) {
res.status(401).send("Unauthorized");
} else {
// Valid; parse and process the event
}
Reliability and delivery
- Respond 2xx within 10 seconds. Your endpoint must be HTTPS and reachable from the public internet. Redirects (3xx) are not followed.
- Retry policy: if you don't answer 2xx or timeout, delivery is retried after 1 minute, 5 minutes, 15 minutes, 1 hour, 3 hours, 6 hours and 12 hours (8 attempts over ~22 hours).
- At-least-once delivery: events may arrive more than once. Deduplicate using the
webhook-idheader (unique per event). Events may arrive out of order; use timestamps if order matters. - Delivery log: check
GET /webhook/deliveriesto see recent attempts, their status codes, response bodies and errors (kept for 30 days).
URL safety
Webhook URLs must be HTTPS and resolve only to public internet addresses. Private addresses (like 10.0.0.1, 192.168.1.x, 127.0.0.1, localhost or cloud-internal networks) are rejected when you save the URL or on every delivery attempt. This protects you from SSRF attacks. Redirects are never followed; the URL you set is the URL we connect to.