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-blue-400 font-semibold">const crypto = require('crypto');
"text-blue-400 font-semibold">const express = require('express');
"text-blue-400 font-semibold">function verifyWebhook(payload, signature, secret) {
"text-blue-400 font-semibold">const hmac = crypto.createHmac('sha256', secret);
hmac.update(payload, 'utf8');
"text-blue-400 font-semibold">const expectedSignature = 'sha256=' + hmac.digest('hex');
"text-blue-400 font-semibold">return crypto.timingSafeEqual(
Buffer.from(signature),
Buffer.from(expectedSignature)
);
}
app.post('/webhook', express.raw({type: 'application/json'}), (req, res) => {
"text-blue-400 font-semibold">const signature = req.get('X-VideoPilot-Signature');
"text-blue-400 font-semibold">const payload = req.body.toString();
"text-blue-400 font-semibold">const isValid = verifyWebhook(payload, signature, process.env.WEBHOOK_SECRET);
"text-blue-400 font-semibold">if (!isValid) {
"text-blue-400 font-semibold">return res.status(401).send('Invalid signature');
}
"text-blue-400 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-blue-400 font-semibold">for rendering
"text-blue-400 font-semibold">await queueVideoForRendering(event.data.video_id);
break;
case 'render.completed':
// Notify user or update database
"text-blue-400 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', 200
Respond 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-blue-400 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/webhook
Webhook Testing Tools
- • webhook.site - Test webhook endpoints without code
- • RequestBin - Inspect webhook payloads
- • ngrok - Local development tunneling
- • Postman - Mock webhook requests