Webhooks
Webhooks allow your application to receive real-time notifications when events occur in VideoPilot. Instead of polling the API for updates, webhooks push event data directly to your server.
Event Occurs
Video generation completes, render finishes, etc.
Webhook Sent
VideoPilot sends event data to your endpoint
You Respond
Your app processes the event and responds with 200 OK
Video Events
Triggered when script generation is complete
{
"event": "video.script_ready",
"data": {
"video_id": "video_123abc",
"title": "Space Discovery",
"script": "In a galaxy far, far away, a brave astronaut begins their journey through the cosmos...",
"status": "script_ready",
"scene_count": 8
},
"timestamp": "2024-01-15T10:30:00.000Z"
}Triggered when all scene media generation is complete
{
"event": "video.media_ready",
"data": {
"video_id": "video_123abc",
"title": "Space Discovery",
"status": "media_ready",
"scene_count": 8,
"total_duration": 87.5,
"scenes_with_images": 8,
"scenes_with_audio": 8
},
"timestamp": "2024-01-15T10:35:00.000Z"
}Triggered when video generation encounters an error
{
"event": "video.generation_failed",
"data": {
"video_id": "video_123abc",
"title": "Space Discovery",
"status": "failed",
"error": "Image generation failed for scene 3",
"failed_step": "image_generation"
},
"timestamp": "2024-01-15T10:25:00.000Z"
}Render Events
Triggered when video rendering begins
{
"event": "render.started",
"data": {
"render_job_id": "render_789xyz",
"video_id": "video_123abc",
"status": "processing",
"quality": "high",
"estimated_duration": 180
},
"timestamp": "2024-01-15T10:40:00.000Z"
}Triggered when video rendering is finished
{
"event": "render.completed",
"data": {
"render_job_id": "render_789xyz",
"video_id": "video_123abc",
"status": "completed",
"output_url": "https://cdn.videopilot.app/videos/video_123abc_high.mp4",
"duration": 87.5,
"file_size": 25690112,
"quality": "high"
},
"timestamp": "2024-01-15T10:43:30.000Z"
}Triggered when video rendering fails
{
"event": "render.failed",
"data": {
"render_job_id": "render_789xyz",
"video_id": "video_123abc",
"status": "failed",
"error": "Rendering timeout after 300 seconds",
"quality": "high"
},
"timestamp": "2024-01-15T10:45:00.000Z"
}Each webhook request includes a signature in the X-VideoPilot-Signature header. Verify this signature to ensure the request comes from VideoPilot:
Node.js/Express Example
"text-primary font-semibold">const crypto = require('crypto');
"text-primary font-semibold">const express = require('express');
"text-primary font-semibold">function verifyWebhook(payload, signature, secret) {
"text-primary font-semibold">const hmac = crypto.createHmac('sha256', secret);
hmac.update(payload, 'utf8');
"text-primary font-semibold">const expectedSignature = 'sha256=' + hmac.digest('hex');
"text-primary font-semibold">return crypto.timingSafeEqual(
Buffer.from(signature),
Buffer.from(expectedSignature)
);
}
app.post('/webhook', express.raw({type: 'application/json'}), (req, res) => {
"text-primary font-semibold">const signature = req.get('X-VideoPilot-Signature');
"text-primary font-semibold">const payload = req.body.toString();
"text-primary font-semibold">const isValid = verifyWebhook(payload, signature, process.env.WEBHOOK_SECRET);
"text-primary font-semibold">if (!isValid) {
"text-primary font-semibold">return res.status(401).send('Invalid signature');
}
"text-primary font-semibold">const event = JSON.parse(payload);
console.log('Received event:', event.event);
// Process the webhook event
switch (event.event) {
case 'video.media_ready':
// Queue "text-primary font-semibold">for rendering
"text-primary font-semibold">await queueVideoForRendering(event.data.video_id);
break;
case 'render.completed':
// Notify user or update database
"text-primary font-semibold">await notifyUserVideoReady(event.data);
break;
}
res.status(200).send('OK');
});Python/Flask Example
import hmac
import hashlib
import os
from flask import Flask, request
app = Flask(__name__)
def verify_webhook(payload, signature, secret):
expected_signature = 'sha256=' + hmac.new(
secret.encode('utf-8'),
payload,
hashlib.sha256
).hexdigest()
return hmac.compare_digest(signature, expected_signature)
@app.route('/webhook', methods=['POST'])
def handle_webhook():
signature = request.headers.get('X-VideoPilot-Signature')
payload = request.get_data()
if not verify_webhook(payload, signature, os.getenv('WEBHOOK_SECRET')):
return 'Invalid signature', 401
event = request.get_json()
print(f"Received event: {event['event']}")
# Process the webhook event
if event['event'] == 'video.media_ready':
# Queue for rendering
queue_video_for_rendering(event['data']['video_id'])
elif event['event'] == 'render.completed':
# Notify user or update database
notify_user_video_ready(event['data'])
return 'OK', 200Respond quickly
Return a 200 status code within 30 seconds. Process heavy work asynchronously.
Handle idempotency
Use the event ID to prevent processing duplicate events.
Verify signatures
Always verify webhook signatures to ensure authenticity.
Handle failures gracefully
VideoPilot will retry failed webhooks up to 3 times with exponential backoff.
Log events
Keep logs of webhook events for debugging and monitoring.
Local Development
Use ngrok or similar tools to expose your local server for webhook testing:
# Install ngrok
"text-primary font-semibold">npm install -g ngrok
# Expose your local server
ngrok http 3000
# Use the HTTPS URL as your webhook endpoint
# https://abc123.ngrok.io/webhookWebhook Testing Tools
- • webhook.site - Test webhook endpoints without code
- • RequestBin - Inspect webhook payloads
- • ngrok - Local development tunneling
- • Postman - Mock webhook requests