file.enrichment.success payload, with the type of its value and a short example. For delivery and signature verification, see Webhooks.
How to read this page
Nested keys are written as paths.file_info.mimetype means a mimetype key inside the file_info object. A [] means the key holds an array, so modules[].module_id is the module_id of each entry in modules.
Types are described plainly: string, number, boolean, object, array, and null. Two qualifiers matter:
Keys are added over time. Ignore any key you do not recognise rather than rejecting the payload.
Top level
event_id is the same on every redelivery of an event, which makes it the right value for recognising a duplicate. Treat it as an opaque string.case_label and file_label are human-readable labels for the case and file. file_label falls back to the name parsed from the S3 upload key when no file label is available; case_label has no such fallback. Each field is an empty string (never null) if nothing could be resolved. They were added to notifications going forward; payloads sent before this addition do not include them.2026-08-25T17:21:40.912430+00:00, rather than a Z suffix.
file_info
mimetype is detected from the contents of the file rather than taken from the upload, so it reflects what the file actually is even when it was sent with a misleading name or content type. Three families of file are analysed:
Word documents are analysed in the
.docx format only, which is why a single value covers them. The legacy .doc format is not supported, and neither are spreadsheets, presentations, or any other format outside the three families above. An unsupported file produces no success payload, so no webhook is delivered for it. See Webhooks for how to detect a file that did not complete.raw_metadata holds whatever metadata the file itself carried, so its keys depend on the file type. The three shapes below share no keys, so check mimetype before reading it.
Images
metadata.tags is free-form: its keys are whatever the file carried, so read the ones you care about by name and expect the rest to vary between files. The c2pa group appears only when the file carries a content credentials manifest.
PDFs
Word documents
analysis_metadata
Raw output from each step of the analysis. Every key below is always present, and isnull when that step does not apply to the file, so an image has no pdf_structure and a PDF has no ela.
What decides whether a section is filled in
Two things, in order. First the file type, which determines whether a step can run at all:ela is calculated for JPEG images only, because it measures compression artefacts that exist only in a lossy format, and pdf_structure and annotations need a PDF. Then the content of the file, which determines whether a step that ran produced anything: barcode_results is null when no barcode could be read, and a scanned or photographed PDF yields no structural analysis because it carries no text to measure.
The same two steps decide which entries appear in modules. A module that depends on one of these sections runs only when that section was produced, so a PNG has no ela and therefore no ela_fraud_detection module, and a file with no readable barcode has no barcode_analysis module. Read results.modules_run for what actually ran rather than assuming a fixed set.
A null section is not a failed one. It means that part of the analysis did not apply to this file, which is the normal case for most files. Where a step was expected and did not complete, a
caveats entry says so.ela
Error Level Analysis, which looks for uneven compression across an image.reverse_search
thumbnail
barcode_results
For barcode types that encode personal data,
value is redacted and the readable content appears in fields instead.ocr
Content read from the file, and the entities recognised in it.classifications[].custom_guess is filled in when category is "Other". The available categories depend on the document classes configured for your organisation.
Entities, in key_persons, key_organizations, key_locations and key_reference_numbers:
raw_ocr_text is filled in when a reading correction was applied, and holds the original text. legible is false when the value could not be read confidently from the image.
Dates, in key_dates:
pdf_structure
dominant_font and dominant_size each pair a value with its occurrence count. font_usage is keyed by font name and size_usage by point size written as a string.
document_summary.error appears instead of every other key in document_summary, when the analysis ran but found no text to measure. When it is present, potential_anomalies is an empty array and none of the font or size keys exist, so read it before any of them. A PDF with no usable text can also arrive as a null pdf_structure instead; see What decides whether a section is filled in.
annotations
Comments, stamps and other annotation objects carried by a PDF, and whether any of them were added after the document itself was created.highlighted_pdf is the path of a copy of the PDF with each reported annotation boxed, and is null when there are no findings. It holds the same value as artifacts.annotated_pdf. Unlike the other timestamps in the payload, analysis_timestamp carries no UTC offset.
Each entry in annotations.analysis.findings:
risk_level is "High" when the annotation’s own timestamp is later than the document’s creation date, meaning it was added after the document was made. It is "Unknown" in every other case, including when either date is missing, so treat "Unknown" as undetermined rather than as safe.
The three technical_details strings are never null. When a value is absent from the annotation a placeholder appears instead, so annotation_text reads "No Content", annotation_author reads "Unknown Author" and annotation_date reads "Unknown". A document that genuinely contains the text “Unknown Author” is therefore indistinguishable from one where the author was not recorded.
annotation_date is written as YYYY-MM-DD HH:MM:SS with no timezone, rather than in the ISO 8601 form used elsewhere in the payload.
Routine annotation objects are left out, including form fields, popup containers and ordinary hyperlinks. A link is reported when it has no destination or carries its own appearance, because that is the shape of a box drawn over existing content. Findings whose boxes overlap are combined into one enclosing box, so a single entry can cover several stacked annotations.
A PDF carrying an annotation added after it was created:
results
The report written for a reviewer.results.findings holds only the findings that fired, summarised for a reviewer:
source_analysis names a modules[].module_id, and custom_alert_type is normally set when alert_type is "OTHER". Either can be null — see Tracing a finding back to its evidence.
modules
One entry per part of the analysis that ran. Which modules appear depends on the file type, its content, and the modules enabled for your organisation, so both the length and the order of this array vary between analyses.
When
modules[].error is set, that module did not complete. Its findings are absent rather than clean, and a caveats entry will normally say so.
modules[].findings holds one entry for every check the module ran, whether or not it fired:
Filter on
found being true to get the alerts that actually triggered. The entries where found is false are the record of what was checked and came back clean, which is useful evidence in itself.module_id you may see include content_verification, manipulation_detection, document_consistency, software_anomalies, reverse_search_findings, pdf_structure_anomaly, barcode_analysis, reference_validation and ela_fraud_detection. Read results.modules_run rather than assuming a fixed set.
Evidence
Findings are backed by two kinds of evidence, which carry different weight.search_evidence is web research, so it corroborates. verification_evidence is a direct check against an authoritative registry or API, so it confirms or contradicts. Keep them distinct when you display them.
search_evidence
verification_evidence
Carries checks only when your organisation’s verification setting was on for the analysis. For an analysis run with it off, a module’sverification_evidence is null or has an empty checks list, results.verification_evidence_summary is null, and no finding carries a v reference.
outputs holds fields specific to whichever source answered, so its keys vary with provider. attests states in one line what a verdict from that source actually proves. extracted is the value as read from the document, before it was normalised into subject.
Tracing a finding back to its evidence
Evidence references live onmodules[].findings only. The findings in results summarise them for a reviewer and carry none.
From a module finding, its evidence_refs point into its own module:
q1.1matches aresults[].result_idin that module’ssearch_evidence. The number after the dot starts at 1, and matches the[1]markers in that query’sanswer.v1matches achecks[].check_idin that module’sverification_evidence.
evidence_refs on a finding that fired means no external source is linked, usually because the finding rests on the file’s own metadata.
From a summary finding, source_analysis gives you the module and alert_type the finding inside it, with custom_alert_type narrowing an "OTHER". That finding’s evidence_refs then resolve as above.
The two evidence fields on a finding do different jobs: evidence[] is written to be read, evidence_refs[] is the link. Reference ids never appear in evidence[].detail.
Cross-module summaries
The same evidence rolled up across modules, so you can show an overview without walking every module.resolved counts checks that got an authoritative answer. failed counts those that could not be verified, which is the is_valid of null case above. Each entry under per_module holds queries, results and cited_result_ids, or checks, in the same shapes as above.
Three things to expect when you compare a summary against the modules it came from:
- The entries are copies, not digests.
per_module["x"]holds the same queries and results as themodules[]entry forx. cited_result_idscovers search only. It is the union of that module’sq*references.v*references are left out, and verification has no equivalent field.- A module can be missing from a summary. Search lists a module that searched and found nothing; verification lists one only if it ran at least one check. Neither lists a module whose evidence gathering timed out.
null rather than an object with zero counts.
artifacts
Files produced by the analysis, given as storage paths rather than URLs. Exchange thefile_id for a short-lived signed URL with Get Asset URL, passing the matching asset_type.
integrity_signals
A summary of the forensic verdict, so you can route a file without walking every module.
Each boolean has three states:
caveats
Limits on how far an analysis should be trusted, such as an authoritative source being unreachable or text being hard to read. Always present; an empty array means no caveats apply. Caveats sit alongside the findings rather than being findings themselves.A
critical caveat on a low-risk analysis is the case worth surfacing: it means the clean verdict itself is not dependable.type is an open list, so handle values beyond the examples above gracefully. scope_ref names whatever the caveat attaches to, such as a check_id.
Reserved fields
Some keys arrive in the payload but are not part of the published contract. They exist for Veridox diagnostics, may change without notice, and should be ignored:This list is not exhaustive, which is why it is worth ignoring keys you do not recognise rather than rejecting the payload.