> For clean Markdown of any page, append .md to the page URL.
> For a complete documentation index, see https://docs.airweave.ai/llms.txt.
> For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://docs.airweave.ai/_mcp/server.

# Setup Guide

> Create webhooks and start receiving events

This guide walks you through setting up webhook subscriptions, handling incoming events, verifying signatures, and managing your event delivery.

## Creating a Subscription

**Mutation endpoints (create, update, delete, recover) require admin or owner role and Bearer token authentication.** API key auth is supported for read-only operations (listing subscriptions, retrieving messages, viewing delivery attempts).

Register your endpoint to start receiving events. You can subscribe to any combination of [event types](/webhooks/types-and-formats) across sync, source connection, and collection lifecycles:

```bash title="cURL"
curl -X POST 'https://api.airweave.ai/webhooks/subscriptions' \
  -H 'Authorization: Bearer <token>' \
  -H 'Content-Type: application/json' \
  -d '{
    "url": "https://your-server.com/webhooks/airweave",
    "event_types": ["sync.completed", "sync.failed", "source_connection.created", "collection.deleted"]
  }'
```

```python title="Python"
import requests

response = requests.post(
    "https://api.airweave.ai/webhooks/subscriptions",
    headers={"Authorization": "Bearer <token>"},
    json={
        "url": "https://your-server.com/webhooks/airweave",
        "event_types": ["sync.completed", "sync.failed", "source_connection.created", "collection.deleted"]
    }
)
subscription = response.json()
print(f"Subscription ID: {subscription['id']}")
```

```javascript title="Node.js"
const response = await fetch("https://api.airweave.ai/webhooks/subscriptions", {
  method: "POST",
  headers: {
    "Authorization": "Bearer <token>",
    "Content-Type": "application/json"
  },
  body: JSON.stringify({
    url: "https://your-server.com/webhooks/airweave",
    event_types: ["sync.completed", "sync.failed", "source_connection.created", "collection.deleted"]
  })
});
const subscription = await response.json();
console.log(`Subscription ID: ${subscription.id}`);
```

**Response:**

```json
{
  "id": "ep_2bVxUn3RFnLYHa8z6ZKHMT9PqPX",
  "url": "https://your-server.com/webhooks/airweave",
  "filter_types": ["sync.completed", "sync.failed", "source_connection.created", "collection.deleted"],
  "disabled": false,
  "created_at": "2025-01-15T10:30:00Z",
  "updated_at": "2025-01-15T10:30:00Z"
}
```

You can optionally provide a custom `secret` (minimum 24 characters) for signature verification. If not provided, a secure secret is auto-generated.

## Handling Incoming Events

Your endpoint receives POST requests with event payloads. Here's how to handle them:

```python title="Python (FastAPI)"
from fastapi import FastAPI, Request

app = FastAPI()

@app.post("/webhooks/airweave")
async def handle_webhook(request: Request):
    event = await request.json()

    match event["event_type"]:
        # Sync events
        case "sync.completed":
            collection = event["collection_name"]
            inserted = event.get("entities_inserted", 0)
            print(f"Sync completed: {collection} ({inserted} entities inserted)")
            # Trigger your downstream workflow here

        case "sync.failed":
            error = event.get("error", "Unknown error")
            collection = event["collection_name"]
            print(f"Sync failed: {collection} - {error}")
            # Send alert or log the failure

        # Source connection events
        case "source_connection.created":
            source = event["source_type"]
            print(f"New {source} connection created")

        case "source_connection.auth_completed":
            source = event["source_type"]
            print(f"{source} connection authenticated")

        case "source_connection.deleted":
            source = event["source_type"]
            print(f"{source} connection removed")

        # Collection events
        case "collection.created":
            print(f"Collection created: {event['collection_name']}")

        case "collection.deleted":
            print(f"Collection deleted: {event['collection_name']}")

    return {"status": "ok"}
```

```javascript title="Node.js (Express)"
const express = require("express");
const app = express();

app.use(express.json());

app.post("/webhooks/airweave", (req, res) => {
  const event = req.body;

  switch (event.event_type) {
    // Sync events
    case "sync.completed":
      console.log(`Sync completed: ${event.collection_name} (${event.entities_inserted} inserted)`);
      break;
    case "sync.failed":
      console.log(`Sync failed: ${event.collection_name} - ${event.error}`);
      break;

    // Source connection events
    case "source_connection.created":
      console.log(`New ${event.source_type} connection created`);
      break;
    case "source_connection.auth_completed":
      console.log(`${event.source_type} connection authenticated`);
      break;
    case "source_connection.deleted":
      console.log(`${event.source_type} connection removed`);
      break;

    // Collection events
    case "collection.created":
      console.log(`Collection created: ${event.collection_name}`);
      break;
    case "collection.deleted":
      console.log(`Collection deleted: ${event.collection_name}`);
      break;
  }

  res.json({ status: "ok" });
});

app.listen(3000);
```

**Respond quickly!** Return `200 OK` immediately and process the event asynchronously. If your endpoint takes too long (30s timeout), the delivery will be marked as failed and retried.

## Signature Verification

Every webhook delivery is signed. Verify signatures to ensure requests actually come from Airweave and haven't been tampered with.

Svix signs every outbound delivery using HMAC-SHA256. It generates a per-endpoint secret, computes the signature over the payload, and attaches `svix-id`, `svix-timestamp`, and `svix-signature` headers. Verifying these signatures is your responsibility as the receiver. You can either implement HMAC-SHA256 verification manually (shown below) or use the Svix client library's `Webhook.verify()` helper.

### Get Your Signing Secret

First, retrieve your subscription's signing secret:

```bash
curl -X GET 'https://api.airweave.ai/webhooks/subscriptions/{subscription_id}?include_secret=true' \
  -H 'x-api-key: YOUR_API_KEY'
```

The response includes a `secret` field in the format `whsec_...`. **Keep this secret secure.**

### Verify the Signature

Events include three headers for verification:

| Header           | Description                |
| ---------------- | -------------------------- |
| `svix-id`        | Unique message identifier  |
| `svix-timestamp` | Unix timestamp of delivery |
| `svix-signature` | HMAC-SHA256 signature      |

```python title="Python"
import hmac
import hashlib
import base64

def verify_signature(payload: bytes, headers: dict, secret: str) -> bool:
    msg_id = headers.get("svix-id")
    timestamp = headers.get("svix-timestamp")
    signature = headers.get("svix-signature")

    if not all([msg_id, timestamp, signature]):
        return False

    # Decode the secret (remove whsec_ prefix)
    secret_bytes = base64.b64decode(secret.replace("whsec_", ""))

    # Build the signed content
    signed_content = f"{msg_id}.{timestamp}.{payload.decode()}"

    # Compute expected signature
    expected = hmac.new(
        secret_bytes,
        signed_content.encode(),
        hashlib.sha256
    ).digest()
    expected_b64 = base64.b64encode(expected).decode()

    # Compare against provided signatures
    for sig in signature.split(","):
        sig_value = sig.split(" ")[-1]  # Handle "v1,..." format
        if hmac.compare_digest(expected_b64, sig_value):
            return True

    return False
```

```javascript title="Node.js"
const crypto = require("crypto");

function verifySignature(payload, headers, secret) {
  const msgId = headers["svix-id"];
  const timestamp = headers["svix-timestamp"];
  const signature = headers["svix-signature"];

  if (!msgId || !timestamp || !signature) return false;

  // Decode the secret (remove whsec_ prefix)
  const secretBytes = Buffer.from(secret.replace("whsec_", ""), "base64");

  // Build the signed content
  const signedContent = `${msgId}.${timestamp}.${payload}`;

  // Compute expected signature
  const expected = crypto
    .createHmac("sha256", secretBytes)
    .update(signedContent)
    .digest("base64");

  // Compare against provided signatures
  return signature.split(",").some(sig => {
    const sigValue = sig.split(" ").pop();
    return crypto.timingSafeEqual(
      Buffer.from(expected),
      Buffer.from(sigValue)
    );
  });
}
```

**Recommended:** Use the official [Svix verification libraries](https://docs.svix.com/receiving/verifying-payloads/how) in production. They handle edge cases, timestamp validation, and replay attack prevention automatically.

## Managing Subscriptions

### List All Subscriptions

```bash
curl -X GET 'https://api.airweave.ai/webhooks/subscriptions' \
  -H 'x-api-key: YOUR_API_KEY'
```

### Get a Specific Subscription

```bash
curl -X GET 'https://api.airweave.ai/webhooks/subscriptions/{subscription_id}' \
  -H 'x-api-key: YOUR_API_KEY'
```

This also returns recent delivery attempts for debugging.

### Update a Subscription

Change the URL, event types, or disable/enable delivery:

```bash title="cURL"
curl -X PATCH 'https://api.airweave.ai/webhooks/subscriptions/{subscription_id}' \
  -H 'Authorization: Bearer <token>' \
  -H 'Content-Type: application/json' \
  -d '{
    "event_types": ["sync.completed", "sync.failed", "source_connection.created", "collection.deleted"],
    "disabled": false
  }'
```

```python title="Python"
response = requests.patch(
    f"https://api.airweave.ai/webhooks/subscriptions/{subscription_id}",
    headers={"Authorization": "Bearer <token>"},
    json={
        "event_types": ["sync.completed", "sync.failed", "source_connection.created", "collection.deleted"],
        "disabled": False
    }
)
```

### Disable a Subscription

Temporarily pause delivery without deleting:

```bash
curl -X PATCH 'https://api.airweave.ai/webhooks/subscriptions/{subscription_id}' \
  -H 'Authorization: Bearer <token>' \
  -H 'Content-Type: application/json' \
  -d '{"disabled": true}'
```

### Enable a Subscription

Re-enable a disabled subscription, optionally recovering missed messages:

```bash
curl -X PATCH 'https://api.airweave.ai/webhooks/subscriptions/{subscription_id}' \
  -H 'Authorization: Bearer <token>' \
  -H 'Content-Type: application/json' \
  -d '{
    "disabled": false,
    "recover_since": "2025-01-15T00:00:00Z"
  }'
```

The `recover_since` parameter is optional. If provided, Airweave will retry all failed messages from that timestamp.

### Delete a Subscription

Permanently remove a subscription:

```bash
curl -X DELETE 'https://api.airweave.ai/webhooks/subscriptions/{subscription_id}' \
  -H 'Authorization: Bearer <token>'
```

This action cannot be undone. Pending deliveries will be cancelled.

## Retrieving Message History

Read-only operations (listing subscriptions, retrieving messages, viewing delivery attempts) support both API key and Bearer token authentication.

### Get All Messages

Retrieve recent webhook messages sent to your organization:

```bash
curl -X GET 'https://api.airweave.ai/webhooks/messages' \
  -H 'x-api-key: YOUR_API_KEY'
```

### Filter by Event Type

```bash
curl -X GET 'https://api.airweave.ai/webhooks/messages?event_types=sync.completed&event_types=sync.failed' \
  -H 'x-api-key: YOUR_API_KEY'
```

### Get a Specific Message

```bash
curl -X GET 'https://api.airweave.ai/webhooks/messages/{message_id}' \
  -H 'x-api-key: YOUR_API_KEY'
```

### Include Delivery Attempts

See exactly what happened during delivery:

```bash
curl -X GET 'https://api.airweave.ai/webhooks/messages/{message_id}?include_attempts=true' \
  -H 'x-api-key: YOUR_API_KEY'
```

## Recovering Failed Messages

If your endpoint was down or had issues, you can replay failed messages:

```bash
curl -X POST 'https://api.airweave.ai/webhooks/subscriptions/{subscription_id}/recover' \
  -H 'Authorization: Bearer <token>' \
  -H 'Content-Type: application/json' \
  -d '{
    "since": "2025-01-15T00:00:00Z",
    "until": "2025-01-16T00:00:00Z"
  }'
```

| Parameter | Required | Description                              |
| --------- | -------- | ---------------------------------------- |
| `since`   | Yes      | Start of recovery window (inclusive)     |
| `until`   | No       | End of recovery window (defaults to now) |

This triggers a recovery task that replays failed messages in chronological order.

## Best Practices

#### Respond fast, process async

Return `200 OK` immediately after receiving a webhook. Queue the event for background processing rather than doing heavy work inline. This prevents timeouts and ensures reliable delivery.

#### Implement idempotency

Webhooks may be delivered more than once. Use the `event_id` or `job_id` field to deduplicate. Store processed event IDs and skip duplicates.

#### Always verify signatures in production

Don't skip signature verification. It protects against:

* Spoofed requests from attackers
* Replay attacks (Svix timestamps help prevent this)
* Data tampering in transit

#### Use HTTPS endpoints

Webhook URLs must use HTTPS in production. This ensures the payload is encrypted in transit. HTTP is only allowed for local development.

#### Log everything

Log incoming payloads, processing results, and any errors. This makes debugging much easier when something goes wrong.

#### Handle all event types gracefully

Even if you only subscribe to specific events like `sync.completed`, your handler should gracefully ignore unknown event types rather than failing. This ensures forward compatibility as new event types are added.

## Sample Application

A sample webhook receiver is available in the Airweave repository under `examples/webhook-demo`. It handles incoming webhooks, verifies signatures, and provides a real-time event viewer for inspecting deliveries as they arrive.

## Next Steps

#### [API Reference](/api-reference/webhooks/get-webhooks-messages)

Explore the full Webhooks API documentation with request/response examples.

#### [Types & Formats](/webhooks/types-and-formats)

Review the detailed payload structures and delivery format.