> ## Documentation Index
> Fetch the complete documentation index at: https://mintlify.com/cvat-ai/cvat/llms.txt
> Use this file to discover all available pages before exploring further.

# Webhooks

> Set up webhooks to receive real-time notifications about CVAT events

## 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.

<Info>
  Webhooks are available for both CVAT Cloud and self-hosted installations.
</Info>

## 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:

```bash theme={null}
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

```python theme={null}
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

| Event            | Description                         |
| ---------------- | ----------------------------------- |
| `create:project` | Project created                     |
| `update:project` | Project updated                     |
| `delete:project` | Project deleted                     |
| `create:task`    | Task created                        |
| `update:task`    | Task updated (includes annotations) |
| `delete:task`    | Task deleted                        |
| `create:job`     | Job created                         |
| `update:job`     | Job updated (includes annotations)  |
| `delete:job`     | Job deleted                         |
| `create:issue`   | Issue created                       |
| `update:issue`   | Issue updated                       |
| `delete:issue`   | Issue deleted                       |
| `create:comment` | Comment created                     |
| `update:comment` | Comment updated                     |
| `delete:comment` | Comment deleted                     |

### Organization Events

| Event                 | Description                    |
| --------------------- | ------------------------------ |
| `update:organization` | Organization settings updated  |
| `delete:organization` | Organization deleted           |
| `create:membership`   | User added to organization     |
| `update:membership`   | Membership role changed        |
| `delete:membership`   | User removed from organization |
| `create:invitation`   | Invitation sent                |
| `delete:invitation`   | Invitation deleted or accepted |

## Webhook Payload

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

### Payload Structure

```json theme={null}
{
  "event": "update:task",
  "webhook_id": 42,
  "sender": {
    "id": 123,
    "username": "john_doe",
    "first_name": "John",
    "last_name": "Doe",
    "email": "john@example.com"
  },
  "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

```json theme={null}
{
  "event": "update:task",
  "webhook_id": 42,
  "sender": {
    "id": 5,
    "username": "annotator1",
    "email": "annotator@example.com"
  },
  "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

```python theme={null}
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

```javascript theme={null}
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](https://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:

```bash theme={null}
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:

```bash theme={null}
# 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

```python theme={null}
# List all webhooks
webhooks = client.webhooks.list()

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

### Update Webhook

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

### View Delivery History

```python theme={null}
# 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

```python theme={null}
# Redeliver a specific delivery
client.webhooks.redeliver(
    webhook_id=42,
    delivery_id=123
)
```

### Delete Webhook

```python theme={null}
# Delete webhook
client.webhooks.delete(webhook_id=42)
```

## Example Integrations

### Slack Notifications

```python theme={null}
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

```python theme={null}
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

<AccordionGroup>
  <Accordion title="Respond Quickly">
    * 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)
  </Accordion>

  <Accordion title="Handle Idempotency">
    * 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
  </Accordion>

  <Accordion title="Monitor Deliveries">
    * Regularly check webhook delivery status
    * Set up alerts for failed deliveries
    * Review and redeliver failed webhooks
    * Update webhook URLs when infrastructure changes
  </Accordion>

  <Accordion title="Secure Your Endpoints">
    * Always verify webhook signatures
    * Use HTTPS with valid certificates
    * Implement rate limiting
    * Log suspicious requests
  </Accordion>
</AccordionGroup>

## 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

* [CVAT API Documentation](https://app.cvat.ai/api/docs)
* [Webhook Best Practices](https://docs.github.com/en/developers/webhooks-and-events/webhooks/best-practices-for-using-webhooks)
* [CVAT Python SDK](https://pypi.org/project/cvat-sdk/)
* [Example Webhook Server](https://github.com/cvat-ai/cvat/tree/develop/tests/webhooks)
