Webhooks
Use webhooks to receive real-time HTTP POST notifications when a render completes or a file finishes processing. Use them instead of polling getRenderProgress/getFileProcessingProgress.
No SDK function registers a webhook. Configure one on an API key, through the dashboard (Settings → API Keys, or editframe.com/resource/api_keys). Set a Webhook URL (this must use HTTPS). Select which Webhook Events (topics) to receive. When you create or update the key, the dashboard generates a Webhook Secret, used to sign deliveries. Copy this secret and store it alongside the API key.
Handling a Webhook
import express from "express";
import crypto from "node:crypto";
const app = express();
// Use express.raw(), not express.json(). Signature verification needs the
// exact raw bytes. Re-serializing parsed JSON can reorder keys or change
// whitespace, which changes the hash and breaks verification.
app.post("/webhooks/editframe", express.raw({ type: "*/*" }), (req, res) => {
const signature = req.headers["x-webhook-signature"] as string;
const rawBody = req.body as Buffer;
const expected = crypto
.createHmac("sha256", process.env.EDITFRAME_WEBHOOK_SECRET!)
.update(rawBody)
.digest("hex");
if (!crypto.timingSafeEqual(Buffer.from(signature, "hex"), Buffer.from(expected, "hex"))) {
return res.status(401).send("Invalid signature");
}
res.status(200).send("OK"); // respond fast — Editframe times out at 30s
const payload = JSON.parse(rawBody.toString("utf-8"));
processWebhookEvent(payload).catch(console.error); // do real work after responding
});
Every request carries an X-Webhook-Signature header: HMAC-SHA256(webhook_secret, raw_json_body), hex-encoded. Verify with crypto.timingSafeEqual, not ===.
Payload
{ topic: string, data: {...} }
Render topics: render.created, render.pending, render.rendering, render.completed, render.failed
data includes id, status, created_at, completed_at, failed_at, width, height, fps, byte_size, duration_ms, md5, metadata, expires_at (null = permanent), download_url (populated once complete), error (populated on failure).
File topics: file.created, file.uploading, file.processing, file.ready, file.failed, file.updated
data includes id, type (video/image/caption), status, filename, byte_size, md5, mime_type, width, height, expires_at. Editframe sends file.updated for a file status change that doesn't match one of the other file topics.
Legacy topics
Editframe still emits these topics, tied to the deprecated per-type file APIs. Do not build a new integration against them.
image_file.created, isobmff_file.created, isobmff_track.created, unprocessed_file.created.
Delivery
- Each event arrives as one HTTP POST, with a JSON body.
- Editframe retries on a fixed 10-second interval, up to 3 attempts total, with a 30-second timeout per attempt. After the final failed attempt, it marks the event failed. This is visible in the dashboard's delivery log. Editframe does not retry or replay the event further.
- Editframe may deliver an event more than once. Key any side effect off
data.id, or off the topic-and-id pair, to stay idempotent. - Always hash the raw request body for signature verification. Parsing the body, then calling
JSON.stringifyagain, can reorder keys or change whitespace. This produces a different hash than the one Editframe signed.
Testing
npx editframe webhook -t render.completed # sends a real test event to the URL configured on your API key
See the editframe-api skill's CLI section for more detail. There is no --webhookURL flag. The target URL always comes from the key's dashboard configuration. The dashboard's API key detail page has an equivalent "Test Webhook" button.
For local development, tunnel your dev server, for example with ngrok http 3000. Point the API key's Webhook URL at the tunnel URL while you test.