# Processing callbacks
Source: https://docs.file.ai/deliver/callbacks

> The payload fileAI POSTs to your callback URL as each processing step of an upload finishes.

fileScout

# Processing callbacks

Copy pageCopy page

The payload fileAI POSTs to your callback URL as each processing step of an upload finishes.

Copy pageCopy page

A processing callback is a `POST` that fileAI sends to your callback URL as an upload moves through processing. It tells you which step finished, whether it succeeded, and, once the upload has been split into documents, the `fileId` of each document, so you can fetch values without polling. This page is the payload reference. To choose between callbacks and polling, and for extract, split, classify and agent job callbacks, see [Async jobs](/api/async-jobs).

Upload callbacks are **sent once and not retried**. If your endpoint is down or returns an error, that event is lost. Keep polling `GET /v1/uploads/{uploadId}` as a fallback. See [Async jobs](/api/async-jobs).

## 

[​

](#how-it-works)

How it works

### 

[​

](#where-the-callback-goes)

Where the callback goes

You set `callbackURL` when you request the upload, or rely on your API key’s default callback URL. Multipart uploads do not fall back to the key’s default. The full rules are under [Upload options](/ingest/uploads#upload-options). Files added in the fileAI app or by email use your organization’s file-processing callback URL, if one is set. Use an HTTPS URL that accepts `POST` with a JSON body. Any `#fragment` on the URL is dropped before sending.

### 

[​

](#what-gets-sent)

What gets sent

Each event is a single `POST` with `Content-Type: application/json`. There are **no other custom headers**: no signature, no timestamp, no event id. fileAI logs your response status but does not act on it, and sets no client timeout of its own. A typical successful upload sends events for these steps, in roughly this order: `processing_started` usually arrives twice: first with `waiting`, then with `completed`. Not every upload passes through every step, and the order is not guaranteed: key your logic on `step` and `status`, not on position. `fileIds` first appears on `split_classify`, and again on `processing_finished`. An upload that fails sends `processing_failed` with status `error` and an `errorReason`.

Reprocessing a document (for example, replacing its file) can send another round of events for the same `uploadId`, including `beethoven_ocr` or `ai_form_filling` with status `processing` and a further `processing_finished`. Make your handler safe to run more than once.

## 

[​

](#example)

Example

A successful upload that produced two documents ends with:

```
{
  "step": "processing_finished",
  "status": "completed",
  "uploadId": "e9818d17-e5be-4d57-bdc8-5a40b4f6f4e1",
  "fileIds": "b765bd00-181e-4737-b4d9-004dadd6bd45,3c1a9f02-77e4-4b1e-9a51-2f0d6c8e4a10"
}
```

A failed upload sends:

```
{
  "step": "processing_failed",
  "status": "error",
  "uploadId": "e9818d17-e5be-4d57-bdc8-5a40b4f6f4e1",
  "errorReason": "ocr_failed"
}
```

A minimal receiver acknowledges fast and does the work afterwards:

Python

Node.js

```
from flask import Flask, request

app = Flask(__name__)

@app.post("/fileai/callback")
def fileai_callback():
    event = request.get_json()
    if event["step"] == "processing_finished" and event["status"] == "completed":
        file_ids = event.get("fileIds", "").split(",")  # a string, not an array
        enqueue_fetch_values(event["uploadId"], file_ids)
    elif event["status"] == "error":
        record_failure(event["uploadId"], event["step"], event.get("errorReason"))
    return "", 200
```

```
import express from "express";

const app = express();
app.use(express.json());

app.post("/fileai/callback", (req, res) => {
  const { step, status, uploadId, fileIds, errorReason } = req.body;
  if (step === "processing_finished" && status === "completed") {
    enqueueFetchValues(uploadId, (fileIds ?? "").split(",")); // a string, not an array
  } else if (status === "error") {
    recordFailure(uploadId, step, errorReason);
  }
  res.sendStatus(200);
});
```

## 

[​

](#reference)

Reference

### 

[​

](#payload-fields)

Payload fields

Field

Type

Present

Description

`step`

string

Always

The processing step this event is about. See below.

`status`

string

Always

`waiting`, `processing`, `completed` or `error`.

`uploadId`

string

Always

The `uploadId` returned when you created the upload.

`fileIds`

string

`split_classify` and `processing_finished`

The `fileId`s of the documents produced from the upload, **joined with commas into one string**. Split it yourself.

`errorReason`

string

Only when there is a reason

Machine-readable failure reason, such as `ocr_failed`, `password_protected` or `corrupted_file`.

There is no `timestamp` field. Record the time you received the event if you need one; `GET /v1/uploads/{uploadId}` returns a `timestamp` per step.

### 

[​

](#statuses)

Statuses

`status`

Meaning

`waiting`

The step is queued. Sent for `processing_started`.

`processing`

The step is running. Sent when a document is reprocessed.

`completed`

The step finished successfully.

`error`

The step failed. On `split_classify`, it means the upload needs attention; check `errorReason`.

### 

[​

](#steps)

Steps

`step`

Sent when

`file_uploaded`

The file has landed in storage.

`processing_started`

The upload enters the processing queue (`waiting`), then starts (`completed`).

`fraud_detection`

An internal processing step has finished. This is not [fileForensics](/forensics/overview).

`first_page_analysis`

First-page analysis has finished.

`beethoven_ocr`

OCR has finished.

`ocr_evaluation`

OCR quality checks have finished.

`split_classify`

The upload has been split into documents and classified. First event carrying `fileIds`.

`ai_form_filling`

A reprocessed document’s values are being extracted again.

`processing_finished`

All documents are processed. Carries `fileIds`.

`processing_failed`

Processing stopped with an error. Carries `errorReason` when one is known.

## 

[​

](#related)

Related

## Async jobs

Upload status polling, and callbacks for extract, split, classify and agent jobs.

## Uploads

Single, batch and multipart uploads, where you set `callbackURL`.

## Incremental loading

Pull new and changed files on a schedule instead of reacting to events.

## API quickstart

Upload a file and read its values end to end.

[Previous](/extract/split-classify)[

Sync changes

Sync new and changed files to a data warehouse with timestamp filters on GET /v1/files.

Next

](/deliver/incremental-loading)

⌘I

## On this page

*   [How it works](#how-it-works)
    *   [Where the callback goes](#where-the-callback-goes)
    *   [What gets sent](#what-gets-sent)
*   [Example](#example)
*   [Reference](#reference)
    *   [Payload fields](#payload-fields)
    *   [Statuses](#statuses)
    *   [Steps](#steps)
*   [Related](#related)
