Getting Started with Webhooks
Configure webhooks to receive real-time notifications when renders complete or files finish processing.
Prerequisites
- An Editframe account with API access
- A publicly accessible HTTPS endpoint to receive webhooks
- Ability to verify HMAC signatures in your application
Step 1: Create a Webhook Endpoint
Create an endpoint in your application that accepts POST requests:
import express from "express";import crypto from "node:crypto";const app = express();app.use(express.json());app.post("/webhooks/editframe", async (req, res) => {const signature = req.headers["x-webhook-signature"];const payload = req.body;// TODO: Verify signature (see Step 3)console.log("Webhook received:", payload.topic);// Respond quickly - process events asynchronouslyres.status(200).send("OK");// Process event in backgroundprocessWebhookEvent(payload).catch(console.error);});app.listen(3000);
Your endpoint must:
- Accept POST requests with JSON body
- Respond with 200 OK within 30 seconds
- Verify the
X-Webhook-Signatureheader - Handle events idempotently (events may be delivered multiple times)
Step 2: Register Your Webhook
Register your webhook URL when creating an API key. You can do this via the Editframe dashboard or programmatically:
Via Dashboard
- Go to editframe.com/resource/api_keys
- Click "Create API Key"
- Fill in the form:
- Name: "My Application"
- Webhook URL:
https://your-app.com/webhooks/editframe - Webhook Events: Select events to receive:
render.completedrender.failedfile.ready
- Click "Create"
- Copy and securely store:
- API Key (for making API requests)
- Webhook Secret (for verifying signatures)
Via API (Programmatic)
import { db } from "@/sql-client.server";import { createApiKey } from "@/createApiKey.server";import { generateApiToken } from "@/util/scryptPromise.server";import crypto from "node:crypto";// Generate tokensconst apiToken = crypto.randomBytes(32).toString("hex");const webhookSecret = crypto.randomBytes(32).toString("hex");// Create API key with webhook configurationconst apiKey = await createApiKey({token: apiToken,webhookSecret: webhookSecret,name: "My Application",orgId: "your-org-id",userId: "your-user-id",webhookUrl: "https://your-app.com/webhooks/editframe",webhookEvents: ["render.completed", "render.failed", "file.ready"],expired_at: null, // or set expiration date});console.log("API Key ID:", apiKey.id);console.log("API Token:", apiToken); // Store securelyconsole.log("Webhook Secret:", webhookSecret); // Store securely
Step 3: Verify Webhook Signatures
Every webhook request includes an X-Webhook-Signature header containing an HMAC-SHA256 signature. Verify this signature to ensure the request is from Editframe:
import crypto from "node:crypto";function verifyWebhookSignature(payload: string,signature: string,secret: string): boolean {const expectedSignature = crypto.createHmac("sha256", secret).update(payload).digest("hex");return crypto.timingSafeEqual(Buffer.from(signature),Buffer.from(expectedSignature));}// In your webhook handlerapp.post("/webhooks/editframe", async (req, res) => {const signature = req.headers["x-webhook-signature"] as string;const payload = JSON.stringify(req.body);const secret = process.env.EDITFRAME_WEBHOOK_SECRET!;if (!verifyWebhookSignature(payload, signature, secret)) {console.error("Invalid webhook signature");return res.status(401).send("Invalid signature");}// Signature is valid - process eventconst { topic, data } = req.body;console.log(`Verified webhook: ${topic}`);res.status(200).send("OK");});
Critical: Hash the raw request body as received, not the parsed JSON object. Different JSON serialization can produce different hashes.
Step 4: Handle Webhook Events
Process events based on their topic:
async function processWebhookEvent(event: WebhookEvent) {const { topic, data } = event;switch (topic) {case "render.completed":console.log(`Render ${data.id} completed`);console.log(`Download URL: ${data.download_url}`);console.log(`Duration: ${data.duration_ms}ms`);console.log(`Size: ${data.byte_size} bytes`);// Download the renderconst response = await fetch(data.download_url);const videoBuffer = await response.arrayBuffer();// ... save or process videobreak;case "render.failed":console.error(`Render ${data.id} failed`);// ... notify user or retrybreak;case "file.ready":console.log(`File ${data.id} is ready`);// ... use file in compositionbreak;default:console.log(`Unhandled webhook topic: ${topic}`);}}
Step 5: Test Your Webhook
Use the Editframe dashboard to send a test webhook:
- Go to your API key detail page
- Click "Test Webhook"
- Select a topic (e.g., "render.completed")
- Click "Send Test"
Your endpoint should receive a test webhook with sample data. Check your server logs to verify the webhook was received and the signature was validated.
Local Development
For local testing, use a tunneling service like ngrok:
# Start your local servernpm run dev# In another terminal, start ngrokngrok http 3000# Use the ngrok URL as your webhook URL# Example: https://abc123.ngrok.io/webhooks/editframe
Update your API key's webhook URL to the ngrok URL, then trigger events to test locally.
See testing.md for more testing strategies.
Production Considerations
When deploying to production:
- Use HTTPS: Webhook URLs must use HTTPS (not HTTP)
- Store secrets securely: Never commit webhook secrets to version control
- Respond quickly: Return 200 OK within 30 seconds
- Process asynchronously: Handle events in background jobs/queues
- Implement idempotency: Use event IDs to prevent duplicate processing
- Log delivery failures: Monitor webhook delivery in the dashboard
- Handle retries: Events may be delivered multiple times
Next Steps
- events.md — Learn about all event types and payload structures
- security.md — Deep dive into signature verification
- testing.md — Advanced testing strategies
- troubleshooting.md — Debug common issues