Document Validated Event
Webhook event fired when all validations for a document settle.
The DOCUMENT_VALIDATED event fires when the custom validations configured for a document settle -- that is, when a validation write completes and nothing is still pending or running. It reports the aggregate validation outcome for the file plus a per-script summary.
When It Triggers
The event fires each time the file's validation state settles: a validation write completes and no validation for that file is PENDING or RUNNING.
Validations can run on two independent paths -- the automatic (static) batch that runs during processing, and validations an agent submits afterward. Because these paths are independent and whether an agent will submit is not knowable in advance, the event can fire more than once for the same file:
- The static batch settles and the event fires with the current outcome.
- If an agent later submits validations, the file settles again and the event fires a second time.
When it fires more than once, the event with the later timestamp supersedes the earlier one. Treat the payload as "the current validation state of this file", keyed by fileId. Delivery is queued, retried and processed concurrently, so receipt order is not state order -- compare timestamp before overwriting what you already stored, or re-read the document with GET /v1/deployments/{deploymentId}/documents/{fileId} instead of applying the event blind. This also holds across the lifecycle events: a later DOCUMENT_VALIDATED supersedes an earlier DOCUMENT_VALIDATION_FAILED.
Unlike the lifecycle events, this one is document-scoped, not attempt-scoped. data.validationAttemptId is the newest attempt that contributed to the document, while each entry in data.results carries the attempt it came from -- after a partial revalidation those differ.
This is a document event, routed to subscriptions by deployment route.
Payload Structure
{
"eventId": "evt_validated_654",
"eventType": "DOCUMENT_VALIDATED",
"timestamp": "2024-01-15T10:40:00Z",
"organizationId": "org_xyz789",
"projectId": "proj_abc123",
"deploymentId": "dep_123abc",
"extractionSchemaId": "schema_456def",
"fileId": "file_789ghi",
"data": {
"status": "failed",
"failCount": 2,
"validationAttemptId": "att_2b",
"results": [
{
"scriptKey": "totals-check",
"source": "STATIC",
"status": "PASSED",
"validationAttemptId": "att_1a"
},
{
"scriptKey": "agent-validator",
"source": "AGENT",
"status": "FAILED",
"validationAttemptId": "att_2b"
}
]
}
}Payload Fields
| Field | Type | Description |
|---|---|---|
eventId | string | Unique event identifier. Use for idempotency. |
eventType | string | Always "DOCUMENT_VALIDATED" |
timestamp | ISO 8601 | When the validation state settled (UTC) |
organizationId | string | Your organization identifier |
projectId | string | The project the document belongs to |
deploymentId | string | The model or deployment that processed this document |
extractionSchemaId | string | The extraction schema ID of that model or deployment |
fileId | string | The validated file identifier |
data.status | "passed" | "failed" | "error" | Aggregate outcome. failed takes precedence over error, which takes precedence over passed. |
data.failCount | number | The file's validation fail count after recompute |
data.validationAttemptId | string | null | The newest attempt id on the file. Correlates with the DOCUMENT_VALIDATION_STARTED and DOCUMENT_VALIDATION_FAILED events of that attempt. null for attempts started before attempt ids existed. |
data.results | array | Per-script summary (see below) |
data.results[].scriptKey | string | Identifier of the validation script |
data.results[].source | "STATIC" | "AGENT" | Whether the validation came from the automatic batch or an agent submission |
data.results[].status | string | Per-script status, e.g. "PASSED", "FAILED", "RESEND_PASSED", "RESEND_FAILED", "ERROR" |
data.results[].validationAttemptId | string | null | The attempt this individual result belongs to. null for results written before attempt ids existed. |
Attempt Ids Can Differ Within One Payload
data.validationAttemptId is the newest attempt id on the file, not a guarantee that every result belongs to it. A partial re-validation -- {"include": ["AGENT"]} or {"include": ["STATIC"]} -- starts a new attempt for one lane only and leaves the other lane's rows on their previous attempt. The settled payload then mixes attempts: the re-run lane carries the new id, the untouched lane carries the older one.
Compare data.results[].validationAttemptId against data.validationAttemptId when you need to know which results are fresh. Results whose attempt id is older were carried over from an earlier attempt and are still the current state of that script -- they are not stale in the sense of being wrong, only in the sense of not having been re-run.
Full Findings Are Not in the Payload
The payload carries a summary per script, not the full validation findings. Detailed findings are intentionally excluded for payload size, retry, and signing reasons. To get the complete findings, call the get-file-data endpoint using the fileId from the payload.
Handling the Event
function handleDocumentValidated(event) {
const { eventId, fileId, data } = event
console.log(`Document ${fileId} validation settled: ${data.status} (failCount: ${data.failCount})`)
if (data.status === "passed") {
fetchFileData(fileId).then(exportToDownstreamSystems)
} else {
const failed = data.results.filter(r => r.status.includes("FAILED"))
routeToReview(fileId, failed)
}
}