DeepFellow DOCS

Webhooks

Receive signed HTTP callbacks when a vector store file batch finishes, instead of polling for its status.

Webhooks notify your application when a vector store file batch reaches a terminal status, so you don't have to repeatedly poll GET /v1/vector_stores/{vector_store_id}/file_batches/{batch_id} to find out when files are ready.

Configure a Webhook

A webhook is configured per project, on the project's webhook_url field. Set it when creating a project (POST /v1/organization/projects) or updating one (POST /v1/organization/projects/{project_id}):

curl -X 'POST' \
  "https://deepfellow-server-host/v1/organization/projects/68da445c5186deb8bca2bde9" \
  -H "Authorization: Bearer DEEPFELLOW-ORGANIZATION-API-KEY" \
  -H 'Content-Type: application/json' \
  -d '{
  "webhook_url": "https://your-app.example.com/webhooks/deepfellow"
}'
import requests

response = requests.post(
    "https://deepfellow-server-host/v1/organization/projects/68da445c5186deb8bca2bde9",
    json={
        "webhook_url": "https://your-app.example.com/webhooks/deepfellow",
    },
    headers={
        "Content-Type": "application/json",
        "Authorization": "Bearer DEEPFELLOW-ORGANIZATION-API-KEY",
    },
)

print(response.json())
const response = await fetch(
    'https://deepfellow-server-host/v1/organization/projects/68da445c5186deb8bca2bde9',
    {
        method: 'POST',
        headers: {
            'Content-Type': 'application/json',
            Authorization: 'Bearer DEEPFELLOW-ORGANIZATION-API-KEY'
        },
        body: JSON.stringify({
            webhook_url: 'https://your-app.example.com/webhooks/deepfellow'
        })
    }
);

const data = await response.json();
console.log(data);

webhook_url must be an HTTPS URL that doesn't target localhost or a private, loopback, link-local, or otherwise non-public address.

If webhook_secret isn't set alongside webhook_url, DeepFellow generates one automatically. The response to project creation is the only place the plaintext secret is ever returned:

{
    "id": "68da445c5186deb8bca2bde9",
    "name": "Simplito",
    "webhook_url": "https://your-app.example.com/webhooks/deepfellow",
    "webhook_secret": "whsec_2b6b8b7a7e...",
    "..."
}

Every later read of the project masks webhook_secret as ••••••••. Store the plaintext value from the creation response: it's needed to verify incoming deliveries.

Anyone who has webhook_secret can forge deliveries to your endpoint. Store it the same way you'd store an API key, and rotate it by sending a new webhook_secret value in a project update.

To stop receiving webhooks, set webhook_url to null in a project update.

Events

DeepFellow sends a webhook for each of these vector store file batch lifecycle events:

  • vector_store.file_batch.completed: every member file reached a terminal status, and at least one completed.
  • vector_store.file_batch.cancelled: no member file completed, and at least one was cancelled.
  • vector_store.file_batch.failed: every member file failed.

Verify and Parse a Delivery

Each delivery is an HTTP POST to webhook_url, matching OpenAI's webhook conventions (the same envelope and headers used by Svix):

{
    "id": "evt_68ee6e656fef807dd40b2f84",
    "object": "event",
    "created_at": 1760449093,
    "type": "vector_store.file_batch.completed",
    "data": {
        "id": "68ee6e646fef807dd40b2f83",
        "object": "vector_store.file_batch",
        "vector_store_id": "68ee6e626fef807dd40b2f80",
        "project_id": "68da445c5186deb8bca2bde9",
        "status": "completed",
        "file_counts": {
            "cancelled": 0,
            "completed": 2,
            "failed": 0,
            "in_progress": 0,
            "total": 2
        },
        "created_at": 1760449090
    }
}

Every delivery carries these headers:

  • webhook-id: a unique ID for this delivery attempt.
  • webhook-timestamp: the Unix timestamp (in seconds) the delivery was signed at.
  • webhook-signature: v1,<signature>, an HMAC-SHA256 signature over {webhook-id}.{webhook-timestamp}.{raw request body}, computed with webhook_secret and base64-encoded.

To verify a delivery, recompute the signature the same way and compare it to webhook-signature:

import base64
import hashlib
import hmac

def verify_webhook(webhook_id: str, timestamp: str, body: bytes, signature_header: str, secret: str) -> bool:
    signed_content = f"{webhook_id}.{timestamp}.{body.decode()}".encode()
    digest = hmac.new(secret.encode(), signed_content, hashlib.sha256).digest()
    expected = f"v1,{base64.b64encode(digest).decode()}"
    return hmac.compare_digest(expected, signature_header)

Reject any delivery whose signature doesn't match before acting on its contents.

Delivery and Retries

Delivery is best-effort: DeepFellow retries a failed delivery with a bounded number of attempts. If RabbitMQ is enabled (DF_RABBITMQ_ENABLED=true), deliveries go through a durable queue from the start, and a failed delivery's retries move to a dead-letter queue with a much longer retry window, consumed by the background nightshift worker. Without RabbitMQ, retries only happen in-process, over a shorter window.

Because delivery isn't guaranteed to be instantaneous or exactly-once, treat a missed or duplicate delivery as a hint to check the current state with GET /v1/vector_stores/{vector_store_id}/file_batches/{batch_id} rather than the sole source of truth.

We use cookies on our website. We use them to ensure proper functioning of the site and, if you agree, for purposes such as analytics, marketing, and targeting ads.