---
title: Webhooks | Developer Documentation
description: Configure LlamaCloud webhooks to receive POST notifications on parse, extract, and classify job events, covering event filtering, custom headers, payload signing, payload format, and retry behavior.
---

Webhooks allow you to receive real-time notifications when events occur in your LlamaCloud jobs. Instead of continuously polling for status updates, you can configure webhook endpoints to be notified immediately when jobs complete, fail, or reach other states.

## Overview

LlamaCloud webhooks provide:

- **Real-time notifications** for jobs
- **Configurable event filtering** to receive only relevant events
- **Retry logic** with exponential backoff for reliability
- **Custom headers** support for authentication
- **Payload signing** so you can verify deliveries are authentic and untampered

## Supported Events

Currently, LlamaCloud supports the following webhook events:

### Extract Events

- `extract.pending` - Extract job has been queued and is waiting to be processed
- `extract.success` - Extract job completed successfully
- `extract.error` - Extract job failed with an error
- `extract.partial_success` - Extract job completed with some warnings or partial failures
- `extract.cancelled` - Extract job was cancelled before completion

### Parse Events

- `parse.pending` - Parse job has been queued and is waiting to be processed
- `parse.success` - Parse job completed successfully
- `parse.error` - Parse job failed with an error
- `parse.partial_success` - Parse job completed with some warnings or partial failures
- `parse.cancelled` - Parse job was cancelled before completion

### Classify Events

- `classify.pending` - Classify job has been queued and is waiting to be processed
- `classify.running` - Classify job has started processing
- `classify.success` - Classify job completed successfully
- `classify.partial_success` - Classify job completed with some warnings or partial failures
- `classify.error` - Classify job failed with an error
- `classify.cancelled` - Classify job was cancelled before completion

## Configuration

Reference the full API schema here: <https://api.cloud.llamaindex.ai/redoc#tag/LlamaExtract/operation/run_job_api_v1_extraction_jobs_post>

### Basic Configuration

Configure webhooks by including webhook configurations in your API calls. You will want to include the webhook configurations as follows:

```
{ ...
  "webhook_configurations": [
    {
      "webhook_url": "string",
      "webhook_signing_secret": "string",
      "webhook_headers": {
        "property1": "string",
        "property2": "string"
      },
      "webhook_events": [
        "extract.pending",
        "extract.success",
        "extract.error"
      ],
      "webhook_output_format": "json"
    }
  ]
  ...
}
```

Here’s how to configure webhooks in Python (added as the webhook\_configurations parameter in the request body):

```
webhook_configurations = [
    {
        "webhook_url": "https://your-domain.com/webhook-endpoint",
        "webhook_events": ["extract.success", "extract.error", "parse.success", "parse.error"],
        "webhook_headers": {
            "Authorization": "Bearer your-token",
            "X-Custom-Header": "custom-value"
        },
        "webhook_output_format": "json"
    }
]
```

### Event Filtering

You can specify which events to receive by setting the `webhook_events` array. If not specified, all events will be sent.

```
# Receive only success and error events
webhook_configurations = [
    {
        "webhook_url": "https://your-domain.com/webhook",
        "webhook_events": ["extract.success", "extract.error", "parse.success", "parse.error"]
        "webhook_output_format": "json"
    }
]


# Receive all events (default behavior)
webhook_configurations = [
    {
        "webhook_url": "https://your-domain.com/webhook"
        "webhook_output_format": "json"
        # webhook_events omitted = receive all events
    }
]
```

### Custom Headers

Add custom headers for authentication or other purposes:

```
webhook_configurations = [
    {
        "webhook_url": "https://your-domain.com/webhook",
        "webhook_headers": {
            "Authorization": "Bearer your-secret-token",
            "X-Source": "llamacloud",
            "Content-Type": "application/json"  # This is set automatically
        }
        "webhook_output_format": "json"
    }
]
```

## Webhook Payload

When an event occurs, LlamaCloud will send a POST request to your webhook URL with the following payload structure:

```
{
    "event_id": "149744dd-9002-4411-a6c7-9635da372caa",
    "event_type": "parse.success",
    "timestamp": 1753985275.1154444,
    "data": {
        "id": "a9a57884-921e-4ec2-b555-f4e5a97ec02a",
        "job_id": "a9a57884-921e-4ec2-b555-f4e5a97ec02a"
    }
}
```

### Payload Fields

- `event_id`: Unique identifier for this webhook event
- `event_type`: The type of event that occurred (e.g., “extract.success”, “parse.success”)
- `timestamp`: Unix timestamp when the event occurred
- `data`: Event-specific data containing job details and results

### HTTP Headers

LlamaCloud includes these headers with webhook requests:

- `Content-Type: application/json`
- `User-Agent: llamaindex-webhook-service/1.0`
- `X-Webhook-Event-ID: {event_id}`
- `X-Webhook-Event-Type: {event_type}`
- `LC-Signature: sha256={signature}` — present only when a `webhook_signing_secret` is configured (see [Securing webhooks](#securing-webhooks-with-signatures))
- Any custom headers you configured

## Securing webhooks with signatures

To verify that a webhook delivery genuinely originated from LlamaCloud and was not tampered with in transit, configure a `webhook_signing_secret` for the endpoint. When a signing secret is set, every request is signed and the signature is sent in the `LC-Signature` header.

The signature is an HMAC-SHA256 hex digest of the **raw request body**, keyed by your signing secret, with a `sha256=` prefix. The `sha256=` prefix names the algorithm, so the scheme can evolve without renaming the header.

```
webhook_configurations = [
    {
        "webhook_url": "https://your-domain.com/webhook",
        "webhook_signing_secret": "your-shared-secret",
        "webhook_events": ["parse.success", "parse.error"],
        "webhook_output_format": "json",
    }
]
```

### Verifying a delivery

Recompute the HMAC over the raw request body with your secret and compare it to the header value using a constant-time comparison. Always compute the signature over the **raw bytes** of the request body — do not re-serialize the parsed JSON first, as that can change the bytes and break verification.

```
import hashlib
import hmac


def is_valid_signature(secret: str, body: bytes, signature_header: str) -> bool:
    """Validate the LC-Signature header against the raw request body."""
    expected = "sha256=" + hmac.new(secret.encode(), body, hashlib.sha256).hexdigest()
    # Constant-time comparison avoids leaking the digest via timing.
    return hmac.compare_digest(expected, signature_header)
```

A FastAPI receiver, for example:

```
from fastapi import FastAPI, Header, HTTPException, Request


app = FastAPI()
WEBHOOK_SECRET = "your-shared-secret"


@app.post("/webhook")
async def receive_webhook(
    request: Request,
    lc_signature: str = Header(default=""),
) -> dict:
    body = await request.body()  # raw bytes, before JSON parsing
    if not is_valid_signature(WEBHOOK_SECRET, body, lc_signature):
        raise HTTPException(status_code=401, detail="Invalid signature")
    # signature verified — safe to process
    return {"ok": True}
```

Treat the secret like any other credential: store it securely, never commit it to source control, and rotate it if it may have been exposed.

## Source IP Addresses

LlamaCloud delivers webhooks from a fixed, region-specific egress IP address. If your webhook endpoint is behind a firewall or enforces network access rules, add the IP address for your account’s region to your allowlist so deliveries are accepted:

| Region             | IP Address      |
| ------------------ | --------------- |
| US / North America | `52.5.178.213`  |
| EU                 | `18.196.227.29` |

These addresses can change in rare cases, so do not rely on them as your only verification mechanism. To authenticate that a request genuinely came from LlamaCloud, configure a `webhook_signing_secret` and validate the [signature](#securing-webhooks-with-signatures) on each incoming webhook (and/or validate custom headers such as an `Authorization` bearer token).

## Retry Behavior

LlamaCloud implements automatic retry logic for webhook deliveries:

- **Maximum attempts**: 3 attempts by default
- **Exponential backoff**: Wait time doubles between attempts (1s, 2s, 4s)
- **Maximum wait time**: 60 seconds maximum between retries
- **Timeout**: 30-second timeout per request

A webhook delivery is considered successful if your endpoint returns any HTTP status code in the 200-299 range.

## Example: Receiving Webhooks with Inngest

You can point `webhook_url` at an [Inngest webhook](https://www.inngest.com/docs/platform/webhooks) to receive LlamaCloud events without running your own server.

In the Inngest dashboard, create a webhook and add this Transform Function so each event is named by its `event_type`:

```
function transform(evt) {
  return { name: `Llamacloud/${evt.event_type}`, data: evt };
}
```

Then use the Inngest URL as your `webhook_url`:

```
webhook_configurations = [
    {
        "webhook_url": "https://inn.gs/e/<your-inngest-key>",
        "webhook_events": ["parse.success", "parse.error"],
        "webhook_output_format": "json"
    }
]
```

Subscribe an Inngest function to the transformed event and fetch the parsed result with the `job_id` from the payload:

```
inngest.createFunction(
  { id: "llamacloud-parse-success" },
  { event: "Llamacloud/parse.success" },
  async ({ event }) => {
    const jobId = event.data.data.job_id;
    const res = await fetch(
      `https://api.cloud.llamaindex.ai/api/v2/parse/${jobId}?expand=markdown`,
      { headers: { Authorization: `Bearer ${process.env.LLAMA_CLOUD_API_KEY}` } },
    );
    const { markdown } = await res.json();
    // do something with the parsed markdown
  },
);
```
