Skip to main content

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

1

Provide Webhook URL

When creating a video, include your webhook URL
{
  "type": "class",
  "script": "Your script...",
  "webhook": "https://yoursite.com/api/webhook"
}
2

Video Processing

SlideVid processes your video (typically 2-5 minutes)
3

Receive Notification

When complete, SlideVid sends a POST request to your webhook URL with video details
4

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
string
Event type: project.completed or project.failed
projectId
string
The project/video ID
status
string
Status: completed or failed
video
object
Video details (only present if status is completed)
metadata
object
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.
message
string
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

webhook.site

Get a temporary webhook URL for testinghttps://webhook.site

requestbin.com

Inspect webhook payloadshttps://requestbin.com

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 projectId
const 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 asynchronously
app.post('/webhook', async (req, res) => {
  // Respond immediately
  res.status(200).send('OK');
  
  // Process in background
  processWebhookAsync(req.body);
});

Webhook Events

Currently supported events:
EventDescription
project.completedVideo successfully generated and ready to download
project.failedVideo generation failed due to an error

Next Steps