Document Classified Event
Webhook event fired when a classifier reaches a decision on a document.
The DOCUMENT_CLASSIFIED event fires when a classifier reaches a decision on a document. It carries both outcomes: the document routed to a deployment, or it did not.
There is no separate "unclassified" event. Read data.status to tell the two apart. This is deliberate -- a consumer that subscribes to classification results should never silently miss the documents that failed to route.
When It Triggers
- Routed -- the classifier picked a deployment and the document moved into it.
data.statusis"classified"androutedDeploymentIdnames the deployment. - Not routed -- the classifier looked at the document and declined to route it.
data.statusis"unclassified",routedDeploymentIdisnull, anddata.unclassifiedReasonsays why. - Routed by hand -- someone routed the document manually from the classifier inbox.
data.sourceis"MANUAL"anddata.manualRoutedByis the user who did it.
This is a classifier event, routed to subscriptions by classifier route, not by deployment. That matters: a document that did not route has no deployment to key off.
Payload Structure
{
"eventId": "evt_classified_101",
"eventType": "DOCUMENT_CLASSIFIED",
"timestamp": "2024-01-15T10:31:05Z",
"organizationId": "org_xyz789",
"projectId": "proj_abc123",
"classifierId": "clf_123abc",
"classificationId": "cls_456def",
"fileId": "file_789ghi",
"routedDeploymentId": "dep_123abc",
"extractionSchemaId": "schema_456def",
"data": {
"status": "classified",
"confidenceScore": 0.94,
"decisionConfidence": 0.91,
"reasoning": "Header carries a VAT number and an invoice total",
"unclassifiedReason": null,
"consideredDeploymentId": null,
"source": "AUTOMATIC",
"manualRoutedBy": null
}
}An unclassified outcome on the same event:
{
"eventId": "evt_classified_102",
"eventType": "DOCUMENT_CLASSIFIED",
"timestamp": "2024-01-15T10:32:11Z",
"organizationId": "org_xyz789",
"projectId": "proj_abc123",
"classifierId": "clf_123abc",
"classificationId": "cls_789jkl",
"fileId": "file_012mno",
"routedDeploymentId": null,
"extractionSchemaId": null,
"data": {
"status": "unclassified",
"confidenceScore": 0.22,
"decisionConfidence": 0.88,
"reasoning": "Closest match was the delivery note schema, but no line items were present",
"unclassifiedReason": "Confidence below the routing bar",
"consideredDeploymentId": "dep_999zzz",
"source": "AUTOMATIC",
"manualRoutedBy": null
}
}Payload Fields
| Field | Type | Description |
|---|---|---|
eventId | string | Unique event identifier. Use for idempotency. |
eventType | string | Always "DOCUMENT_CLASSIFIED" |
timestamp | ISO 8601 | When the decision was reached (UTC) |
organizationId | string | Your organization identifier |
projectId | string | The project the classifier belongs to. See the note below for global classifiers. |
classifierId | string | The classifier that made the decision |
classificationId | string | This classification run |
fileId | string | The classified file. Stable across routing -- the same id before and after the document moves into a deployment. |
routedDeploymentId | string | null | The deployment the document routed into, or null when it did not route |
extractionSchemaId | string | null | The extraction schema of that deployment, or null when it did not route |
data.status | "classified" | "unclassified" | The outcome |
data.confidenceScore | number | null | How confident the classifier is in the match, 0--1 |
data.decisionConfidence | number | null | How confident the classifier is in the decision itself, including a decision not to route |
data.reasoning | string | null | The classifier's own explanation |
data.unclassifiedReason | string | null | Why the document did not route. null when it did. |
data.consideredDeploymentId | string | null | A deployment the classifier weighed and rejected. Never treat this as a routing target. |
data.source | "AUTOMATIC" | "MANUAL" | Whether the classifier or a person made the call |
data.manualRoutedBy | string | null | The user who routed it by hand, when source is "MANUAL" |
consideredDeploymentId is not a route
When a document does not route, the classifier may still have had a leading candidate. That candidate appears as data.consideredDeploymentId, never as routedDeploymentId. Treating it as a route would send the document somewhere the classifier explicitly declined to send it.
projectId for a global classifier
A global classifier is shared across every project in your organization, so a single classification can be delivered to subscriptions in several projects at once. When the document routed, projectId is the routed deployment's project and every subscriber sees the same value. When it did not route there is no such project, so each subscription receives its own projectId — the project the webhook is configured in. classifierId, classificationId and fileId are the same for every subscriber and are the safest keys for your own bookkeeping.
Handling the Event
function handleDocumentClassified(event) {
const { fileId, routedDeploymentId, data } = event
if (data.status === "classified") {
console.log(`${fileId} routed to ${routedDeploymentId} (${data.confidenceScore})`)
return
}
console.warn(`${fileId} not routed: ${data.unclassifiedReason}`)
routeToManualTriage(fileId, data.reasoning)
}Notes
An unclassified outcome is not an error. If you retry on it, you will re-run the same classifier over the same document and get the same answer. Use DOCUMENT_CLASSIFICATION_FAILED to catch the runs that genuinely broke.