Skip to main content

Overview

Webhooks allow you to receive real-time HTTP callbacks when specific events occur in CVAT. This enables you to build custom integrations, automate workflows, and trigger actions based on CVAT activities.
Webhooks are available for both CVAT Cloud and self-hosted installations.

What are Webhooks?

Webhooks are HTTP callbacks triggered by events in CVAT. When an event occurs (like task creation, annotation updates, or job completion), CVAT sends an HTTP POST request to your specified endpoint with event details. Common use cases:
  • Send notifications to Slack or Discord when tasks are completed
  • Trigger automated quality checks after annotation updates
  • Update external project management systems
  • Start model training pipelines when datasets reach certain thresholds
  • Log annotation activities to analytics platforms
  • Sync data with external databases

Webhook Types

CVAT supports two types of webhooks:

Organization Webhooks

Receive events from all resources within an organization:
  • Project events (create, update, delete)
  • Task events (create, update, delete)
  • Job events (create, update, delete)
  • Issue events (create, update, delete)
  • Comment events (create, update, delete)
  • Organization events (update, delete)
  • Membership events (create, update, delete)
  • Invitation events (create, delete)

Project Webhooks

Receive events only from a specific project:
  • Task events (create, update, delete)
  • Job events (create, update, delete)
  • Issue events (create, update, delete)
  • Comment events (create, update, delete)
  • Project events (update, delete)

Creating a Webhook

Via CVAT UI

  1. Navigate to the Webhooks page in CVAT
  2. Click Create webhook
  3. Configure the webhook:
    • Target URL: Your endpoint URL (must be HTTPS)
    • Description: Optional description for identification
    • Type: Organization or Project
    • Project: (If Project type) Select the project
    • Events: Select which events to subscribe to
    • Content Type: application/json (default)
    • Secret: Optional secret for signature verification
    • Enable SSL: Verify SSL certificates (recommended)
  4. Click Create

Via API

Create a webhook using the REST API:
curl -X POST https://app.cvat.ai/api/webhooks \
  -H "Authorization: Token YOUR_API_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "target_url": "https://your-server.com/webhook",
    "description": "Task completion notifications",
    "type": "project",
    "project_id": 123,
    "events": "update:task,delete:task",
    "secret": "your-secret-key",
    "is_active": true,
    "enable_ssl": true
  }'

Via Python SDK

from cvat_sdk import make_client

client = make_client(
    host="https://app.cvat.ai",
    credentials=("username", "password")
)

# Create organization webhook
webhook = client.webhooks.create(
    target_url="https://your-server.com/webhook",
    description="Organization events",
    type="organization",
    events=["create:task", "update:task", "delete:task"],
    secret="your-secret-key",
    is_active=True,
    enable_ssl=True
)

print(f"Created webhook: {webhook.id}")

Available Events

Subscribe to specific events based on your needs:

Resource Events

EventDescription
create:projectProject created
update:projectProject updated
delete:projectProject deleted
create:taskTask created
update:taskTask updated (includes annotations)
delete:taskTask deleted
create:jobJob created
update:jobJob updated (includes annotations)
delete:jobJob deleted
create:issueIssue created
update:issueIssue updated
delete:issueIssue deleted
create:commentComment created
update:commentComment updated
delete:commentComment deleted

Organization Events

EventDescription
update:organizationOrganization settings updated
delete:organizationOrganization deleted
create:membershipUser added to organization
update:membershipMembership role changed
delete:membershipUser removed from organization
create:invitationInvitation sent
delete:invitationInvitation deleted or accepted

Webhook Payload

When an event occurs, CVAT sends a POST request with this payload structure:

Payload Structure

{
  "event": "update:task",
  "webhook_id": 42,
  "sender": {
    "id": 123,
    "username": "john_doe",
    "first_name": "John",
    "last_name": "Doe",
    "email": "[email protected]"
  },
  "task": {
    "id": 456,
    "name": "Street scene annotation",
    "status": "annotation",
    "project_id": 789,
    "organization": 1,
    "created_date": "2024-01-15T10:30:00Z",
    "updated_date": "2024-01-15T15:45:00Z",
    "owner": {...},
    "assignee": {...}
  },
  "before_update": {
    "status": "new"
  }
}

Payload Fields

  • event: The event type (e.g., create:task, update:job)
  • webhook_id: ID of the webhook that triggered
  • sender: User who performed the action
  • [resource]: The affected resource (task, job, project, etc.)
  • before_update: (Update events only) Previous values of changed fields

Example: Task Update Event

{
  "event": "update:task",
  "webhook_id": 42,
  "sender": {
    "id": 5,
    "username": "annotator1",
    "email": "[email protected]"
  },
  "task": {
    "id": 100,
    "name": "Vehicle Detection - Batch 3",
    "status": "completed",
    "project_id": 10,
    "size": 500,
    "jobs": [...]
  },
  "before_update": {
    "status": "annotation",
    "updated_date": "2024-01-15T14:00:00Z"
  }
}

Securing Webhooks

Signature Verification

CVAT signs webhook payloads using HMAC-SHA256. Verify signatures to ensure requests are from CVAT:

Python Example

import hmac
import hashlib
import json
from flask import Flask, request, abort

app = Flask(__name__)
WEBHOOK_SECRET = "your-secret-key"

@app.route("/webhook", methods=["POST"])
def webhook_handler():
    # Get signature from header
    signature = request.headers.get("X-Signature-256")
    if not signature:
        abort(401)
    
    # Compute expected signature
    payload = request.get_data()
    expected_signature = "sha256=" + hmac.new(
        WEBHOOK_SECRET.encode(),
        payload,
        hashlib.sha256
    ).hexdigest()
    
    # Compare signatures
    if not hmac.compare_digest(signature, expected_signature):
        abort(401)
    
    # Process webhook
    data = request.json
    event_type = data["event"]
    
    if event_type == "update:task":
        handle_task_update(data)
    
    return {"status": "success"}, 200

def handle_task_update(data):
    task_id = data["task"]["id"]
    old_status = data.get("before_update", {}).get("status")
    new_status = data["task"]["status"]
    
    print(f"Task {task_id} status changed: {old_status} -> {new_status}")

Node.js Example

const express = require('express');
const crypto = require('crypto');

const app = express();
const WEBHOOK_SECRET = 'your-secret-key';

app.use(express.json());

app.post('/webhook', (req, res) => {
  const signature = req.headers['x-signature-256'];
  
  if (!signature) {
    return res.status(401).send('No signature');
  }
  
  const payload = JSON.stringify(req.body);
  const expectedSignature = 'sha256=' + crypto
    .createHmac('sha256', WEBHOOK_SECRET)
    .update(payload)
    .digest('hex');
  
  if (!crypto.timingSafeEqual(
    Buffer.from(signature),
    Buffer.from(expectedSignature)
  )) {
    return res.status(401).send('Invalid signature');
  }
  
  // Process webhook
  const { event, task } = req.body;
  console.log(`Received event: ${event}`);
  
  res.json({ status: 'success' });
});

app.listen(3000, () => {
  console.log('Webhook server running on port 3000');
});

SSL/TLS

Always use HTTPS endpoints and enable SSL verification:
  • Use valid SSL certificates (not self-signed)
  • Keep the Enable SSL option checked in webhook settings
  • Use certificate pinning for additional security

IP Allowlisting

For self-hosted CVAT, restrict webhook endpoints to your CVAT server IP addresses.

Testing Webhooks

Using Webhook.site

Test webhooks without setting up a server:
  1. Go to webhook.site
  2. Copy your unique URL
  3. Create a webhook in CVAT with this URL
  4. Trigger an event in CVAT
  5. View the received payload on webhook.site

Ping Endpoint

Test webhook connectivity using the ping endpoint:
curl -X POST https://app.cvat.ai/api/webhooks/42/ping \
  -H "Authorization: Token YOUR_API_TOKEN"
This sends a test ping event to your webhook URL.

Local Testing with ngrok

Test webhooks on your local machine:
# Install ngrok
brew install ngrok  # macOS
# or download from https://ngrok.com

# Start your local server
python webhook_server.py  # Running on localhost:5000

# Create tunnel
ngrok http 5000

# Use the ngrok URL (e.g., https://abc123.ngrok.io) in your webhook

Webhook Management

List Webhooks

# List all webhooks
webhooks = client.webhooks.list()

for webhook in webhooks:
    print(f"{webhook.id}: {webhook.description} -> {webhook.target_url}")

Update Webhook

# Update webhook configuration
client.webhooks.update(
    webhook_id=42,
    is_active=False,  # Disable webhook
    events=["create:task", "delete:task"]  # Change events
)

View Delivery History

# Get delivery history for a webhook
deliveries = client.webhooks.deliveries(webhook_id=42)

for delivery in deliveries:
    print(f"Event: {delivery.event}")
    print(f"Status: {delivery.status_code}")
    print(f"Date: {delivery.created_date}")
    print(f"Response: {delivery.response}")

Redeliver Failed Webhooks

# Redeliver a specific delivery
client.webhooks.redeliver(
    webhook_id=42,
    delivery_id=123
)

Delete Webhook

# Delete webhook
client.webhooks.delete(webhook_id=42)

Example Integrations

Slack Notifications

import requests
from flask import Flask, request

app = Flask(__name__)
SLACK_WEBHOOK_URL = "https://hooks.slack.com/services/YOUR/WEBHOOK/URL"

@app.route("/cvat-webhook", methods=["POST"])
def cvat_webhook():
    data = request.json
    event = data["event"]
    
    if event == "update:task":
        task = data["task"]
        old_status = data.get("before_update", {}).get("status")
        new_status = task["status"]
        
        if new_status == "completed":
            message = {
                "text": f"Task completed: {task['name']}",
                "attachments": [{
                    "color": "good",
                    "fields": [
                        {"title": "Task ID", "value": task["id"], "short": True},
                        {"title": "Status", "value": new_status, "short": True}
                    ]
                }]
            }
            requests.post(SLACK_WEBHOOK_URL, json=message)
    
    return {"status": "ok"}, 200

if __name__ == "__main__":
    app.run(port=5000)

Automated Quality Checks

from flask import Flask, request
from cvat_sdk import make_client

app = Flask(__name__)

@app.route("/cvat-webhook", methods=["POST"])
def quality_check():
    data = request.json
    
    if data["event"] == "update:job":
        job = data["job"]
        
        # If job status changed to completed, run quality check
        if job["status"] == "completed":
            client = make_client(
                host="https://app.cvat.ai",
                credentials=("username", "password")
            )
            
            # Download annotations
            job_obj = client.jobs.retrieve(job["id"])
            annotations = job_obj.get_annotations()
            
            # Run custom quality checks
            issues = run_quality_checks(annotations)
            
            # Create issues for problems found
            for issue in issues:
                client.issues.create(
                    job_id=job["id"],
                    message=issue["message"],
                    frame=issue["frame"]
                )
    
    return {"status": "ok"}, 200

def run_quality_checks(annotations):
    # Implement your quality check logic
    issues = []
    # ...
    return issues

if __name__ == "__main__":
    app.run(port=5000)

Best Practices

  • Return a 200 response within 10 seconds
  • Process webhook data asynchronously if needed
  • Use job queues for long-running tasks
  • CVAT will retry failed deliveries (5xx errors)
  • Webhooks may be delivered multiple times
  • Use event IDs or timestamps to detect duplicates
  • Design handlers to be idempotent
  • Store processed event IDs to avoid reprocessing
  • Regularly check webhook delivery status
  • Set up alerts for failed deliveries
  • Review and redeliver failed webhooks
  • Update webhook URLs when infrastructure changes
  • Always verify webhook signatures
  • Use HTTPS with valid certificates
  • Implement rate limiting
  • Log suspicious requests

Troubleshooting

Webhook Not Firing

  • Verify webhook is active (is_active: true)
  • Check event type is in the webhook’s subscribed events
  • Ensure the resource is within scope (organization/project)
  • Review webhook configuration for typos

Failed Deliveries

  • Check target URL is accessible from CVAT servers
  • Verify SSL certificate is valid
  • Ensure endpoint returns 2xx status code
  • Check server logs for errors
  • Review timeout settings (webhooks timeout after 10s)

Missing Events

  • Some events may not fire for bulk operations
  • Check the before_update field to detect actual changes
  • Review CVAT logs for event processing

Signature Verification Failing

  • Ensure secret matches exactly (no extra spaces)
  • Use raw request body for signature computation
  • Verify HMAC algorithm is SHA256
  • Check header name is X-Signature-256

Additional Resources

Build docs developers (and LLMs) love