Video API Webhooks

Receive real-time notifications for your video processing workflow events.

Webhook Information EVENT NOTIFICATIONS
How to Use Webhooks

Set up a publicly accessible endpoint on your server to receive webhook notifications.

When you create a video processing job, include your webhook URL in the request parameters.

We'll send real-time updates to your endpoint as your video job progresses.

Status Updates

Your webhook endpoint will receive notifications with the following status values:

  • downloading - When we begin downloading your source video
  • processing - When video processing/conversion begins
  • uploading - When we begin uploading the processed video
  • completed - When the entire process completes successfully
  • failed - When an error occurs at any stage
Example Payload

Each webhook notification will contain a JSON payload with the following format:

{ "uuid": "a1b2c3d4-e5f6-7890-abcd-1234567890ab", "status": "processing", "timestamp": "2025-04-09T15:30:45Z" }
Security

All webhook requests are signed using HMAC-SHA256 to ensure authenticity.

The signature is included in the X-Webhook-Signature HTTP header.

To verify the signature, compute an HMAC-SHA256 hash of the request body using your webhook secret as the key.

API Examples
Django
Flask
Ruby
Express
FastApi
PHP
Python Client Example

        import json
        from django.http import JsonResponse
        from django.views.decorators.csrf import csrf_exempt
        import hmac
        import hashlib

        @csrf_exempt
        def webhook_receiver(request):
            try:
                import hmac
                import hashlib
                import base64
                import json

                # Get the request data
                data = json.loads(request.body)
                payload = data.get('data')
                received_signature = data.get('signature')

                if not payload or not received_signature:
                    return JsonResponse({"status": "error", "message": "Missing data or signature"}, status=400)

                # For testing, use a hardcoded token
                test_token = ""

                # Convert payload to JSON string in the same way as the sender
                payload_json = json.dumps(payload, sort_keys=True)

                # Compute the expected signature
                expected_signature = hmac.new(
                    key=test_token.encode('utf-8'),
                    msg=payload_json.encode('utf-8'),
                    digestmod=hashlib.sha256
                ).digest()

                # Base64 encode for comparison
                expected_signature_b64 = base64.b64encode(expected_signature).decode('utf-8')

                # Verify signatures match using constant-time comparison to prevent timing attacks
                if not hmac.compare_digest(expected_signature_b64, received_signature):
                    return JsonResponse({"status": "error", "message": "Invalid signature"}, status=403)

                # Process the webhook data
                request_id = payload.get('uuid')  # Updated to match the sender's payload structure
                status = payload.get('status')
                timestamp = payload.get('timestamp')

                # Log all received data for testing purposes
                print("Webhook received with payload:", payload)
                print(f"Request ID: {request_id}")
                print(f"Status: {status}")
                print(f"Timestamp: {timestamp}")

                # Return all data for verification purposes
                return JsonResponse({
                    "status": "success",
                    "message": f"Webhook processed for request {request_id}",
                    "received_data": payload
                })

            except json.JSONDecodeError:
                return JsonResponse({"status": "error", "message": "Invalid JSON"}, status=400)
            except Exception as e:
                import traceback
                print(traceback.format_exc())
                return JsonResponse({"status": "error", "message": f"Unexpected error: {str(e)}"}, status=500)
    
Flask Client Example

        import json
        from flask import Flask, request, jsonify
        import hmac
        import hashlib
        import base64

        app = Flask(__name__)

        @app.route('/webhook', methods=['POST'])
        def webhook_receiver():
            try:
                # Get the request data
                if not request.is_json:
                    return jsonify({"status": "error", "message": "Request must be JSON"}), 400

                data = request.get_json()
                payload = data.get('data')
                received_signature = data.get('signature')

                if not payload or not received_signature:
                    return jsonify({"status": "error", "message": "Missing data or signature"}), 400

                # For testing, use a hardcoded token
                test_token = ""

                # Convert payload to JSON string in the same way as the sender
                payload_json = json.dumps(payload, sort_keys=True)

                # Compute the expected signature
                expected_signature = hmac.new(
                    key=test_token.encode('utf-8'),
                    msg=payload_json.encode('utf-8'),
                    digestmod=hashlib.sha256
                ).digest()

                # Base64 encode for comparison
                expected_signature_b64 = base64.b64encode(expected_signature).decode('utf-8')

                # Verify signatures match using constant-time comparison to prevent timing attacks
                if not hmac.compare_digest(expected_signature_b64, received_signature):
                    return jsonify({"status": "error", "message": "Invalid signature"}), 403

                # Process the webhook data
                request_id = payload.get('uuid')
                status = payload.get('status')
                timestamp = payload.get('timestamp')

                # Log all received data for testing purposes
                app.logger.info("Webhook received with payload: %s", payload)
                app.logger.info("Request ID: %s", request_id)
                app.logger.info("Status: %s", status)
                app.logger.info("Timestamp: %s", timestamp)

                # Return all data for verification purposes
                return jsonify({
                    "status": "success",
                    "message": f"Webhook processed for request {request_id}",
                    "received_data": payload
                })

            except json.JSONDecodeError:
                return jsonify({"status": "error", "message": "Invalid JSON"}), 400
            except Exception as e:
                import traceback
                app.logger.error("Error processing webhook: %s\n%s", str(e), traceback.format_exc())
                return jsonify({"status": "error", "message": f"Unexpected error: {str(e)}"}), 500

        if __name__ == '__main__':
            app.run(debug=True)
    
Ruby on Rails Client Example

        # app/controllers/webhooks_controller.rb
        require 'openssl'
        require 'base64'
        require 'json'

        class WebhooksController < ApplicationController
          # Skip CSRF protection for webhooks
          skip_before_action :verify_authenticity_token

          def receive
            begin
              # Parse the request body
              request_body = request.body.read
              data = JSON.parse(request_body)

              payload = data['data']
              received_signature = data['signature']

              if payload.nil? || received_signature.nil?
                return render json: { status: 'error', message: 'Missing data or signature' }, status: 400
              end

              # For testing, use a hardcoded token
              test_token = ''

              # Convert payload to JSON string in the same way as the sender
              # Sort keys to ensure consistent ordering
              payload_json = JSON.generate(payload.sort.to_h)

              # Compute the expected signature
              digest = OpenSSL::HMAC.digest('SHA256', test_token, payload_json)
              expected_signature = Base64.strict_encode64(digest)

              # Verify signatures match using constant-time comparison to prevent timing attacks
              if !ActiveSupport::SecurityUtils.secure_compare(expected_signature, received_signature)
                return render json: { status: 'error', message: 'Invalid signature' }, status: 403
              end

              # Process the webhook data
              request_id = payload['uuid']
              status = payload['status']
              timestamp = payload['timestamp']

              # Log all received data for testing purposes
              Rails.logger.info("Webhook received with payload: #{payload}")
              Rails.logger.info("Request ID: #{request_id}")
              Rails.logger.info("Status: #{status}")
              Rails.logger.info("Timestamp: #{timestamp}")

              # Return all data for verification purposes
              render json: {
                status: 'success',
                message: "Webhook processed for request #{request_id}",
                received_data: payload
              }

            rescue JSON::ParserError
              render json: { status: 'error', message: 'Invalid JSON' }, status: 400
            rescue StandardError => e
              Rails.logger.error("Webhook error: #{e.message}\n#{e.backtrace.join("\n")}")
              render json: { status: 'error', message: "Unexpected error: #{e.message}" }, status: 500
            end
          end
        end

        # config/routes.rb
        Rails.application.routes.draw do
          post '/webhook', to: 'webhooks#receive'
        end
    
Express.js Client Example

        const express = require('express');
        const crypto = require('crypto');
        const bodyParser = require('body-parser');

        const app = express();
        app.use(bodyParser.json());

        app.post('/webhook', (req, res) => {
            try {
                const data = req.body;
                const payload = data.data;
                const receivedSignature = data.signature;

                if (!payload || !receivedSignature) {
                    return res.status(400).json({
                        status: "error",
                        message: "Missing data or signature"
                    });
                }

                // For testing, use a hardcoded token
                const testToken = "";

                // Convert payload to JSON string in the same way as the sender
                const payloadJson = JSON.stringify(payload, Object.keys(payload).sort());

                // Compute the expected signature
                const hmac = crypto.createHmac('sha256', testToken);
                hmac.update(payloadJson);
                const expectedSignature = hmac.digest('base64');

                // Verify signatures match using constant-time comparison to prevent timing attacks
                const signaturesMatch = crypto.timingSafeEqual(
                    Buffer.from(expectedSignature),
                    Buffer.from(receivedSignature)
                );

                if (!signaturesMatch) {
                    return res.status(403).json({
                        status: "error",
                        message: "Invalid signature"
                    });
                }

                // Process the webhook data
                const requestId = payload.uuid;
                const status = payload.status;
                const timestamp = payload.timestamp;

                // Log all received data for testing purposes
                console.log("Webhook received with payload:", payload);
                console.log(`Request ID: ${requestId}`);
                console.log(`Status: ${status}`);
                console.log(`Timestamp: ${timestamp}`);

                // Return all data for verification purposes
                return res.status(200).json({
                    status: "success",
                    message: `Webhook processed for request ${requestId}`,
                    received_data: payload
                });

            } catch (error) {
                console.error('Webhook processing error:', error);
                return res.status(500).json({
                    status: "error",
                    message: `Unexpected error: ${error.message}`
                });
            }
        });

        const PORT = process.env.PORT || 3000;
        app.listen(PORT, () => {
            console.log(`Server listening on port ${PORT}`);
        });
    
FastAPI Client Example

        from fastapi import FastAPI, Request, HTTPException
        from fastapi.responses import JSONResponse
        from pydantic import BaseModel
        from typing import Dict, Any, Optional
        import hmac
        import hashlib
        import base64
        import json
        import logging

        app = FastAPI()

        # Configure logging
        logging.basicConfig(level=logging.INFO)
        logger = logging.getLogger(__name__)

        class WebhookPayload(BaseModel):
            data: Dict[str, Any]
            signature: str

        @app.post("/webhook")
        async def webhook_receiver(request: Request):
            try:
                # Get the raw request body
                body = await request.body()

                # Parse the JSON manually (not using the Pydantic model directly for raw access)
                data = json.loads(body)
                payload = data.get('data')
                received_signature = data.get('signature')

                if not payload or not received_signature:
                    raise HTTPException(
                        status_code=400,
                        detail={"status": "error", "message": "Missing data or signature"}
                    )

                # For testing, use a hardcoded token
                test_token = ""

                # Convert payload to JSON string in the same way as the sender
                payload_json = json.dumps(payload, sort_keys=True)

                # Compute the expected signature
                expected_signature = hmac.new(
                    key=test_token.encode('utf-8'),
                    msg=payload_json.encode('utf-8'),
                    digestmod=hashlib.sha256
                ).digest()

                # Base64 encode for comparison
                expected_signature_b64 = base64.b64encode(expected_signature).decode('utf-8')

                # Verify signatures match using constant-time comparison to prevent timing attacks
                if not hmac.compare_digest(expected_signature_b64, received_signature):
                    raise HTTPException(
                        status_code=403,
                        detail={"status": "error", "message": "Invalid signature"}
                    )

                # Process the webhook data
                request_id = payload.get('uuid')
                status = payload.get('status')
                timestamp = payload.get('timestamp')

                # Log all received data for testing purposes
                logger.info(f"Webhook received with payload: {payload}")
                logger.info(f"Request ID: {request_id}")
                logger.info(f"Status: {status}")
                logger.info(f"Timestamp: {timestamp}")

                # Return all data for verification purposes
                return {
                    "status": "success",
                    "message": f"Webhook processed for request {request_id}",
                    "received_data": payload
                }

            except json.JSONDecodeError:
                raise HTTPException(
                    status_code=400,
                    detail={"status": "error", "message": "Invalid JSON"}
                )
            except Exception as e:
                import traceback
                logger.error(f"Error processing webhook: {str(e)}\n{traceback.format_exc()}")
                raise HTTPException(
                    status_code=500,
                    detail={"status": "error", "message": f"Unexpected error: {str(e)}"}
                )

        if __name__ == "__main__":
            import uvicorn
            uvicorn.run(app, host="0.0.0.0", port=8000)
    
PHP Client Example

         'error',
                    'message' => 'Invalid JSON: ' . json_last_error_msg()
                ]);
                exit;
            }

            // Extract payload and signature
            $payload = $data['data'] ?? null;
            $receivedSignature = $data['signature'] ?? null;

            // Validate required fields
            if (empty($payload) || empty($receivedSignature)) {
                http_response_code(400);
                echo json_encode([
                    'status' => 'error',
                    'message' => 'Missing data or signature'
                ]);
                exit;
            }

            // For testing, use a hardcoded token
            $testToken = '';

            // Convert payload to JSON string in the same way as the sender
            // Make sure to sort the keys for consistent ordering
            ksort($payload);
            $payloadJson = json_encode($payload);

            // Compute the expected signature
            $expectedSignature = base64_encode(
                hash_hmac('sha256', $payloadJson, $testToken, true)
            );

            // Verify signatures match using constant-time comparison to prevent timing attacks
            if (!hash_equals($expectedSignature, $receivedSignature)) {
                http_response_code(403);
                echo json_encode([
                    'status' => 'error',
                    'message' => 'Invalid signature'
                ]);
                exit;
            }

            // Process the webhook data
            $requestId = $payload['uuid'] ?? 'unknown';
            $status = $payload['status'] ?? 'unknown';
            $timestamp = $payload['timestamp'] ?? 'unknown';

            // Log all received data for testing purposes
            error_log("Webhook received with payload: " . print_r($payload, true));
            error_log("Request ID: $requestId");
            error_log("Status: $status");
            error_log("Timestamp: $timestamp");

            // Return all data for verification purposes
            http_response_code(200);
            echo json_encode([
                'status' => 'success',
                'message' => "Webhook processed for request $requestId",
                'received_data' => $payload
            ]);

        } catch (Exception $e) {
            // Log the error
            error_log("Webhook error: " . $e->getMessage() . "\n" . $e->getTraceAsString());

            // Return error response
            http_response_code(500);
            echo json_encode([
                'status' => 'error',
                'message' => 'Unexpected error: ' . $e->getMessage()
            ]);
        }
        ?>