Skip to main content

How It Works

Webhooks let you receive automatic notifications when events occur in Veridox, such as a file analysis completing.
1

Create a webhook configuration

An organisation owner creates a webhook configuration via the API, providing a destination URL where you want to receive events.
2

Store your signing secret

Veridox provisions a signing secret (format: whsec_{32-char-alphanumeric}) and returns it once in the creation response. After that, only a prefix is shown for identification.
The secret is only shown once at creation time. Store it securely; you cannot retrieve it again.
3

Receive events

When events occur, Veridox sends a POST request to your URL with:
  • A JSON payload in the body
  • An x-vdx-signature header containing an HMAC-SHA256 hex digest of the raw request body, signed with your webhook secret
4

Verify and process

You verify the signature on your server to confirm the request genuinely came from Veridox and hasn’t been tampered with, then process the event.

Event Types

Analysis failures are not currently delivered to configured webhook endpoints. To detect a file that did not complete, poll the file’s analysis_status via List Files or subscribe to case progress.
Every payload carries an event_type. Branch on it rather than assuming a single shape, so that new event types added later do not break your receiver.

Delivery Semantics

A 2xx is terminal. It stops all further retries for that event. Send one only once you have taken responsibility for the event, because nothing will redeliver it afterwards.
Use event_id to recognise a repeat delivery. It is the same on every retry of an event. trace_id identifies the analysis rather than the delivery, and completed_at is not unique.

Responding

Keep your response inside the 30-second window and hand the event to your own code for anything slower. What matters is only this:
  • Return 2xx once you have taken responsibility for the event. This stops redelivery.
  • Return any non-2xx, or nothing at all, if you could not accept it. This asks for a retry, and you have roughly 24 hours of them.
Because a non-2xx simply schedules the next attempt, returning an error while you are unavailable is safe. The same event comes back every 30 minutes until you accept it.

Payload Structure

The example below is abridged; arrays are truncated and some sections are collapsed. See the Webhook Payload Reference for every key, its type and an example value.

Absent keys versus null

Handling these two cases wrongly is the most common integration bug:
  • Optional top-level sections may be absent, or present with the value null. This applies to results, integrity_signals, highlights, ocr_summary, and trace_id. Treat both forms identically and use a null-safe accessor.
  • Nested keys are always present, carrying null where a step did not apply to the file. Everything inside analysis_metadata, artifacts, file_info, and each module follows this rule.
  • caveats and modules are always arrays, never null.
Do not rely on which form you receive for an optional section. Checking only "results" in payload, or only payload.results !== null, will break on the other form. In TypeScript prefer payload.results?.overall_risk_score; in Python prefer (payload.get("results") or {}).
Do not index into modules or findings by position. Which modules run depends on the file type and your organisation’s enabled modules, so both the length and the ordering vary between analyses. Look modules up by module_id, and findings by alert_type.

Field notes

Module Evidence

Each entry in modules carries its findings alongside the evidence that supports them: The two carry different weight and are kept separate for that reason. search_evidence is web research with a synthesised answer, so it is corroborative. verification_evidence is a direct check against an authoritative source, so it is definitive. Show them distinctly to your reviewers rather than combining them into one evidence list.
In a verification check, is_valid: null means “could not be verified”, not “invalid”. It usually means the authoritative source was unreachable or does not cover the subject, and a matching entry in caveats will normally say so. Presenting it as a negative result will mislead your reviewers.
A finding’s evidence_refs are IDs that point back into its module’s evidence, so you can resolve each claim to its source:
  • IDs like q1.1 resolve against search_evidence.results, matched on result_id. The trailing index is 1-based and matches the [1] citation markers in the query’s answer.
  • IDs like v1 resolve against verification_evidence.checks, matched on check_id.
The results.search_evidence_summary and results.verification_evidence_summary objects roll the same evidence up across modules, keyed by module_id, which is useful for rendering an overview without walking every module.
Both summary objects may be null, and a query’s answer may be null. Treat them as optional when parsing.

Analysis Caveats

The caveats array records limits on how far an analysis should be trusted, such as an authoritative source being unreachable or text recognition quality being poor. Each caveat carries a severity of info, warn, or critical. A critical caveat on an otherwise clean analysis is the case worth surfacing to your reviewers: it means the clean verdict itself is not dependable. See caveats for every field.

Signature Verification

The signature is computed as:
You compare the x-vdx-signature header value against your own computed HMAC. Always use a timing-safe comparison to prevent timing attacks.
Sign the raw request bytes, exactly as received. Parsing the JSON and re-serialising it changes the bytes (key order, whitespace, unicode escaping) and the signature will not match. Configure your framework to give you the raw body on this route: express.raw() in Express, or disable body parsing for the webhook path.

Best Practices

Store secrets securely because the signing secret is shown once at creation time and cannot be retrieved again
Always verify signatures against the raw request bytes before doing anything with the payload
Use timing-safe comparison to prevent timing attacks
Respond within 30 seconds, and do slower work outside the request
Recognise repeat deliveries using event_id, which is the same on every retry of an event
Ignore keys you do not recognise so that keys added later do not break your receiver
Only one active webhook configuration is allowed per organisation at a time. Creating a new configuration deactivates the previous one.