# Webhooks

Human URL: https://dochorizon.klippa.com/docs/capturing/async/webhooks
Source: https://dochorizon.klippa.com/docs/llms/capturing/async/webhooks.md

> Connect to your own application by configuring the webhook object

---

Webhooks let Doxis AI.dp notify your server when an async job changes state or completes, so you don't have to poll for status. A webhook is configured per request by including a `webhook` object in the body of an async API call.

Webhook requests originate from a fixed set of [trusted IP addresses](https://dochorizon.klippa.com/docs/llms/api/trusted-ip.md).

### Event types

Subscribe to one or more of the following events by listing them in the `types` array.

### on_finish

Sent once when the job reaches a terminal state. The payload contains the full parser response, the same shape you would get from the sync endpoint.

### on_status_update

Sent every time the job's status changes (for example `in_queue` → `processing` → `hitl` → `done`). Use this to drive progress UI or track long-running Human in the Loop (HITL) reviews.

### on_error

Sent when the job's status changes to `error`.

### on_reject

Sent when the job is rejected during Human in the Loop review.

> **INFO:** `on_hold` is a job *status*, not an event type. When a review is placed on hold, an `on_status_update` webhook is delivered with `data.status` set to `on_hold` and the `hitl` object populated with the on-hold details.

### The Webhook Object

To enable webhooks, add a Webhook object to the request body of an async API call. The webhook object looks like the following:

To configure a valid webhook object, the following fields are **required**:

- **url**: The URL where webhooks will be sent.
- **types**: The webhook events you want to monitor (see above).

You may also include these optional fields for additional customization:

- **auth**: For authentication, configure either `authorization_header` or `basic_auth`.
- **custom_headers**: Additional headers sent on every webhook call for that job, as key-value pairs.

> **WARNING:** Only one form of authentication is allowed. You cannot combine `authorization_header` and `basic_auth` on the same webhook.

### Authentication

Doxis AI.dp supports two authentication modes on outgoing webhooks. Choose one per webhook.

### Authorization header

Sends an `Authorization` header with the token type and value you provide. Typical values are `Bearer` tokens, but any type string is accepted.

### Basic auth

Sends an `Authorization: Basic <base64>` header built from a username and password.

### Retries

If your endpoint does not respond with a 2xx status, Doxis AI.dp retries the webhook with exponential backoff. Each webhook is retried up to 16 times before it is considered permanently failed. After the final attempt, the delivery is dropped; Doxis AI.dp does not surface failed webhook state back through the job.

> **INFO:** Your endpoint must be reachable from the internet and return a 2xx response within a reasonable timeout. Return as quickly as you can and process the payload asynchronously on your side.

### Security recommendations

- Use HTTPS for the webhook URL so credentials and payloads are encrypted in transit.
- Prefer an authentication mode (Bearer or Basic) so your endpoint can reject unsigned traffic.
- Validate the job ID in the payload against a job you created, so replayed webhooks cannot inject data.
- Keep custom header values out of public logs.

### Webhook Messages

Each tab below corresponds to a different message type and shows an example of the payload you will receive during the async process.

```json
{
  "webhook_type": "on_finish",
  "job_id": "cldgvf2h1qsc73bboum0",
  "data": {
    "components": {
      "amount_details": null,
      "barcode": null,
      "chain_liability": null,
      "data_matching": null,
      "date_details": null,
      "document_classification": {
        "candidates": null,
        "value": [
          {
            "classification": "financial",
            "confidence": 1
          },
          {
            "classification": "invoice",
            "confidence": 0.97
          },
          {
            "classification": "other",
            "confidence": 1
          }
        ]
      },
      "document_country_code": null,
      "document_language": null,
      "financial": {
        "candidates": null,
        "currency": "EUR",
        "customer": {
          "candidates": null,
          "company_name": "Pok Org.",
          "components": {
            "relation_address": null,
            "relation_details": null,
            "relation_matching": null
          },
          "person": null
        },
        "document_date": "2023-11-03T00:00:00",
        "invoice_number": null,
        "merchant": {
          "brand_name": "Klippacast B.V.",
          "candidates": null,
          "company_name": null,
          "components": {
            "relation_address": null,
            "relation_details": null,
            "relation_matching": null
          }
        },
        "tax_details": null,
        "total_amount": 20.5
      },
      "hash": null,
      "keyword_matching": null,
      "line_items": {
        "line_item_sections": [
          {
            "items": [
              {
                "amount": 20.5,
                "amount_each": 20.5,
                "amount_each_ex_vat": 16.94,
                "amount_ex_vat": 16.94,
                "amount_sub_total": 0,
                "candidates": null,
                "commission": 0,
                "components": {
                  "line_item_matching": null,
                  "matched_data_sources": null
                },
                "country_of_origin": "",
                "currency": "EUR",
                "date": "",
                "description": "",
                "discount_amount": 0,
                "discount_code": "",
                "discount_percentage": 0,
                "ean": "",
                "gross_weight": 0,
                "hs_code": "",
                "line_number": "1",
                "matched_purchase_order": null,
                "net_weight": 0,
                "order_number": "",
                "period": "",
                "product_type": "",
                "quantity": 1,
                "sku": "",
                "title": "Service fee",
                "unit_of_measurement": "",
                "vat_amount": 3.56,
                "vat_code": "",
                "vat_percentage": 21
              }
            ],
            "title": ""
          }
        ]
      },
      "ocr": null,
      "payment_details": null,
      "project_code": null,
      "reference_details": null,
      "transport_details": null,
      "travel_details": null
    },
    "version": "v1.0.0"
  }
}
```

```json
{
  "data": {
    "create_time": "2023-11-20T07:50:20.649653Z",
    "status": "processing",
    "job_id": "cldgvf2h1qsc73bboum0",
    "hitl": null
  },
  "webhook_type": "on_status_update",
  "job_id": "cldgvf2h1qsc73bboum0"
}
```

```json
{
  "data": {
    "create_time": "2023-11-20T07:59:41.139585Z",
    "status": "hitl",
    "job_id": "cldh3rah1qsc73bbouu0",
    "hitl": {
      "finished_number_of_rounds": 0,
      "total_number_of_rounds": 1,
      "rejected": false,
      "reject_reason": null
    }
  },
  "webhook_type": "on_status_update",
  "job_id": "cldh3rah1qsc73bbouu0"
}
```

```json
{
  "webhook_type": "on_status_update",
  "job_id": "cldh3rah1qsc73bbouu0",
  "data": {
    "create_time": "2023-11-20T07:59:41.139585Z",
    "status": "on_hold",
    "job_id": "cldh3rah1qsc73bbouu0",
    "hitl": {
      "finished_number_of_rounds": 0,
      "total_number_of_rounds": 1,
      "rejected": false,
      "reject_reason": null,
      "has_custom_reject_reason": false,
      "custom_reject_reason": null,
      "on_hold": true,
      "on_hold_reason": "needs_more_info",
      "has_custom_on_hold_reason": false,
      "custom_on_hold_reason": null,
      "on_hold_expire_time": "2023-11-22T07:59:41.139585Z",
      "reviews": []
    }
  }
}
```

```json
{
  "data": {
    "create_time": "2023-11-21T10:10:34.495587Z",
    "status": "rejected",
    "job_id": "cle846g4rqfs738eivm0",
    "hitl": {
      "finished_number_of_rounds": 1,
      "total_number_of_rounds": 1,
      "rejected": true,
      "reject_reason": null
    }
  },
  "webhook_type": "on_status_update",
  "job_id": "cle846g4rqfs738eivm0"
}
```

```json
{
  "data": {
    "create_time": "2023-11-21T10:10:34.495587Z",
    "status": "error",
    "job_id": "cle846g4rqfs738eivm0",
    "hitl": {
      "finished_number_of_rounds": 1,
      "total_number_of_rounds": 1,
      "error": true,
      "error_reason": null
    }
  },
  "webhook_type": "on_error_update",
  "job_id": "cle846g4rqfs738eivm0"
}
```

```json
{
  "data": {
    "create_time": "2023-11-21T10:08:53.573328Z",
    "status": "done",
    "job_id": "cle83d84rqfs738eivk0",
    "hitl": {
      "finished_number_of_rounds": 1,
      "total_number_of_rounds": 1,
      "rejected": false,
      "reject_reason": null
    }
  },
  "webhook_type": "on_status_update",
  "job_id": "cle83d84rqfs738eivk0"
}
```