Overview
Webhooks allow you to receive real-time notifications when your video processing completes, instead of polling the API repeatedly.
Recommended : Always use webhooks for production applications. Polling is inefficient and can hit rate limits.
How Webhooks Work
Provide Webhook URL
When creating a video, include your webhook URL {
"type" : "class" ,
"script" : "Your script..." ,
"webhook" : "https://yoursite.com/api/webhook"
}
Video Processing
SlideVid processes your video (typically 2-5 minutes)
Receive Notification
When complete, SlideVid sends a POST request to your webhook URL with video details
Return 200 OK
Your endpoint must return a 200 status code to confirm receipt
Webhook Payload
When your video is ready, you’ll receive this payload:
{
"event" : "project.completed" ,
"projectId" : "proj_abc123" ,
"status" : "completed" ,
"video" : {
"id" : "video_xyz789" ,
"url" : "https://cdn.tryslidevid.ai/videos/xyz789.mp4" ,
"duration" : 45 ,
"thumbnail" : "https://cdn.tryslidevid.ai/thumbnails/xyz789.jpg" ,
"aspectRatio" : "9:16" ,
"resolution" : "1080x1920"
},
"metadata" : {
"courseId" : "python-101" ,
"lessonId" : "lesson-5" ,
"studentId" : "student-123"
},
"createdAt" : "2024-01-15T10:30:00Z" ,
"completedAt" : "2024-01-15T10:33:00Z"
}
Payload Fields
Event type: project.completed or project.failed
Status: completed or failed
Video details (only present if status is completed) Direct download URL for the video (expires in 7 days)
Video duration in seconds
URL to video thumbnail image
Custom data you provided when creating the video. Use this to correlate webhook responses with your internal systems (e.g., course IDs, user IDs, order numbers). Only present if you included metadata in the original request.
Error message (only present if status is failed)
Implementing a Webhook Endpoint
Node.js (Express)
const express = require ( 'express' );
const app = express ();
app . use ( express . json ());
app . post ( '/api/webhook' , async ( req , res ) => {
const { event , projectId , status , video , metadata } = req . body ;
console . log ( `Received webhook: ${ event } for ${ projectId } ` );
if ( event === 'project.completed' && status === 'completed' ) {
// Video is ready!
console . log ( 'Video URL:' , video . url );
console . log ( 'Duration:' , video . duration , 'seconds' );
// Use metadata to correlate with your systems
if ( metadata ) {
console . log ( 'Course ID:' , metadata . courseId );
console . log ( 'Lesson ID:' , metadata . lessonId );
}
try {
// Save to database with your custom identifiers
await saveVideoToDatabase ({
projectId ,
videoUrl: video . url ,
thumbnail: video . thumbnail ,
duration: video . duration ,
// Use metadata to link to your records
courseId: metadata ?. courseId ,
lessonId: metadata ?. lessonId
});
// Notify user
await notifyUser ( projectId , video . url );
// Download and store (optional)
await downloadAndStore ( video . url , projectId );
} catch ( error ) {
console . error ( 'Error processing webhook:' , error );
// Still return 200 to prevent retries for application errors
}
} else if ( event === 'project.failed' ) {
console . error ( 'Video failed:' , req . body . message );
await notifyUserOfFailure ( projectId );
}
// IMPORTANT: Always return 200
res . status ( 200 ). send ( 'OK' );
});
app . listen ( 3000 );
Python (FastAPI)
from fastapi import FastAPI, Request
import httpx
app = FastAPI()
@app.post ( "/api/webhook" )
async def webhook_handler ( request : Request):
payload = await request.json()
event = payload.get( "event" )
project_id = payload.get( "projectId" )
status = payload.get( "status" )
print ( f "Received webhook: { event } for { project_id } " )
if event == "project.completed" and status == "completed" :
video = payload.get( "video" )
# Save to database
await save_video_to_db({
"project_id" : project_id,
"video_url" : video[ "url" ],
"thumbnail" : video[ "thumbnail" ],
"duration" : video[ "duration" ]
})
# Notify user
await notify_user(project_id, video[ "url" ])
elif event == "project.failed" :
await notify_failure(project_id, payload.get( "message" ))
# Always return 200
return { "status" : "ok" }
Next.js API Route
import { NextRequest , NextResponse } from 'next/server' ;
export async function POST ( req : NextRequest ) {
try {
const payload = await req . json ();
const { event , projectId , status , video } = payload ;
if ( event === 'project.completed' && status === 'completed' ) {
// Process completed video
await processCompletedVideo ({
projectId ,
videoUrl: video . url ,
thumbnail: video . thumbnail ,
duration: video . duration
});
}
return NextResponse . json ({ received: true }, { status: 200 });
} catch ( error ) {
console . error ( 'Webhook error:' , error );
// Still return 200 to prevent retries
return NextResponse . json ({ error: 'Internal error' }, { status: 200 });
}
}
Webhook Security
Verify Webhook Source
Always verify webhooks are coming from SlideVid. Check the request origin and consider implementing HMAC signature verification.
// Verify request came from SlideVid's IP ranges
const allowedIPs = [ '52.1.2.3' , '54.2.3.4' ]; // Example IPs
app . post ( '/api/webhook' , ( req , res ) => {
const clientIP = req . ip || req . headers [ 'x-forwarded-for' ];
if ( ! allowedIPs . includes ( clientIP )) {
return res . status ( 403 ). send ( 'Forbidden' );
}
// Process webhook...
});
Use HTTPS
Always use HTTPS for your webhook endpoints to ensure data is encrypted in transit.
✅ https://yoursite.com/webhook
❌ http://yoursite.com/webhook
Secret Tokens
Include a secret token in your webhook URL for additional security:
// When creating video
{
"webhook" : "https://yoursite.com/webhook?token=your_secret_token"
}
// In your webhook handler
app . post ( '/webhook' , ( req , res ) => {
const token = req . query . token ;
if ( token !== process . env . WEBHOOK_SECRET ) {
return res . status ( 401 ). send ( 'Unauthorized' );
}
// Process webhook...
});
Retry Logic
If your endpoint doesn’t return a 200 status code, SlideVid will retry:
1st retry : After 1 minute
2nd retry : After 5 minutes
3rd retry : After 15 minutes
After 3 failed attempts, no more retries will be made.
Best Practices for Retries
app . post ( '/webhook' , async ( req , res ) => {
// Return 200 FIRST
res . status ( 200 ). send ( 'OK' );
// Then process asynchronously
setImmediate ( async () => {
try {
await processWebhook ( req . body );
} catch ( error ) {
// Handle error without affecting the HTTP response
console . error ( 'Async processing error:' , error );
await logToErrorTracking ( error );
}
});
});
Testing Webhooks
Local Development with ngrok
# Install ngrok
npm install -g ngrok
# Start your local server
node server.js # Running on localhost:3000
# Create tunnel
ngrok http 3000
# Use the ngrok URL as your webhook
# https://abc123.ngrok.io/api/webhook
Webhook Testing Sites
Test Webhook Endpoint
# Test your webhook manually
curl -X POST https://yoursite.com/api/webhook \
-H "Content-Type: application/json" \
-d '{
"event": "project.completed",
"projectId": "test_123",
"status": "completed",
"video": {
"url": "https://example.com/test.mp4",
"duration": 30,
"thumbnail": "https://example.com/thumb.jpg"
}
}'
Common Issues
Possible causes:
Webhook URL not publicly accessible
Firewall blocking SlideVid’s IP addresses
Endpoint returning non-200 status code
Solution: Test with webhook.site first, then gradually move to your endpoint
Possible causes:
Your endpoint took too long to respond
Endpoint returned non-200, triggering retry
Solution: Implement idempotency using projectIdconst processedProjects = new Set ();
app . post ( '/webhook' , ( req , res ) => {
const { projectId } = req . body ;
if ( processedProjects . has ( projectId )) {
return res . status ( 200 ). send ( 'Already processed' );
}
processedProjects . add ( projectId );
// Process webhook...
});
Problem: Your endpoint is slow, causing timeoutsSolution: Return 200 immediately, process asynchronouslyapp . post ( '/webhook' , async ( req , res ) => {
// Respond immediately
res . status ( 200 ). send ( 'OK' );
// Process in background
processWebhookAsync ( req . body );
});
Webhook Events
Currently supported events:
Event Description project.completedVideo successfully generated and ready to download project.failedVideo generation failed due to an error
Next Steps