Skip to content

Fileverity API docs

Webhooks

Receive analysis.completed, analysis.failed and balance.low events. Standard Webhooks signature verification in JavaScript and Python, retries, auto-disable and the test event.

View as MarkdownUpdated 2026-11-04

Webhooks send an HTTPS request to your server when something happens, so you do not have to poll. They follow the Standard Webhooks specification.

Add endpoints in the Console under Webhooks. Choose the URL and the events it receives. The signing secret, which starts with whsec_, is shown once when you create the endpoint. You can change the events of an endpoint at any time without changing its secret.

To replace a lost or exposed secret, choose Roll secret on the endpoint. The new secret is shown once and the old one stops working immediately, so update your verifier right away.

  • The URL must use HTTPS on the default port 443 and point to a public address. URLs with credentials or another port are rejected.
  • A workspace can have up to 10 endpoints.
  • Test keys never send events.

Events

EventSent when
analysis.completedAn image or document analysis succeeded.
analysis.failedAn analysis failed or was canceled. It was not charged. A canceled analysis has status: canceled and error: canceled.
balance.lowThe balance fell below the alert threshold you set in the Console.

The supported event types are analysis.completed, analysis.failed, balance.low.

Payloads

Every event has the same envelope: id, object (always event), type, created (Unix timestamp) and data. The id is also sent in the webhook-id header.

analysis.completed and analysis.failed

data is the same analysis object that GET /analyses/{id} returns, with the same field names and types, except that result is always null: fetch the result with that endpoint. status is succeeded, failed or canceled, and error is an object with code and message, or null. Timestamps are Unix seconds.

{  "id": "evt_Lm8sK3tR6yZ2cXaQ9vB4nP1w",  "object": "event",  "type": "analysis.completed",  "created": 1793700131,  "data": {    "id": "an_8Hk2pQ4vN9sL1xR7cW3mY6zB",    "object": "analysis",    "created": 1793700120,    "model": "fvimg-2-2026-10-30",    "livemode": true,    "status": "succeeded",    "file": {      "name": "claim-photo.jpg",      "bytes": 2481920,      "format": "jpeg"    },    "report_id": "FV-7K2M-9QXD-4T8R",    "report_url": "https://fileverity.com/verify/FV-7K2M-9QXD-4T8R",    "summary": {      "level": "L2",      "assessment": "authentic_untouched",      "action": "accept",      "ai": "very_low",      "edit": "none"    },    "result": null,    "error": null,    "progress": null,    "usage": {      "amount": 199,      "currency": "usd",      "funding": "balance"    },    "metadata": {      "order_id": "A-10492"    },    "completed_at": 1793700131,    "expires_at": 1794304931  }}
{  "id": "evt_Zc4vB7nP1sK8tL3wR6yQm2Xa",  "object": "event",  "type": "analysis.failed",  "created": 1793700131,  "data": {    "id": "an_8Hk2pQ4vN9sL1xR7cW3mY6zB",    "object": "analysis",    "created": 1793700120,    "model": "fvimg-2-2026-10-30",    "livemode": true,    "status": "failed",    "file": {      "name": "claim-photo.jpg",      "bytes": 2481920,      "format": "jpeg"    },    "report_id": null,    "report_url": null,    "summary": null,    "result": null,    "error": {      "code": "analysis_failed",      "message": "The analysis could not be completed. You were not charged; retry it, and contact support with the request ID if it fails again."    },    "progress": null,    "usage": {      "amount": 0,      "currency": "usd",      "funding": null    },    "metadata": null,    "completed_at": 1793700135,    "expires_at": null  }}

balance.low

balance and threshold are in US cents.

{  "id": "evt_Lm8sK3tR6yZ2cXaQ9vB4nP1w",  "object": "event",  "type": "balance.low",  "created": 1793700500,  "data": {    "balance": 640,    "threshold": 1000  }}

Verify the signature

Each request carries three headers, as the Standard Webhooks specification defines:

HeaderValue
webhook-idThe event ID. The same across retries; use it to ignore duplicates.
webhook-timestampUnix time in seconds of this delivery attempt. It changes on each retry.
webhook-signaturev1, followed by a base64 HMAC-SHA256. A header can carry several space-separated signatures.

To verify a request:

  1. Read the raw request body as text, before any JSON parsing.
  2. Build the signed content: webhook-id, a dot, webhook-timestamp, a dot and the raw body.
  3. Take your signing secret, remove the whsec_ prefix and base64-decode the rest. That is the HMAC key.
  4. Compute HMAC-SHA256 of the signed content and base64-encode it.
  5. Compare it, in constant time, with each v1 signature in the header.
  6. Reject timestamps more than five minutes from your clock.
import { createHmac, timingSafeEqual } from "node:crypto"; export function verify(secret, headers, body) {  const id = headers["webhook-id"];  const timestamp = headers["webhook-timestamp"];  const signatures = headers["webhook-signature"];  if (!id || !timestamp || !signatures) throw new Error("Missing webhook headers");  if (Math.abs(Date.now() / 1000 - Number(timestamp)) > 300) throw new Error("Timestamp outside tolerance");  const key = Buffer.from(secret.replace(/^whsec_/, ""), "base64");  const expected = createHmac("sha256", key).update(id + "." + timestamp + "." + body).digest();  const valid = signatures.split(" ").some((entry) => {    const [version, value] = entry.split(",");    const given = Buffer.from(value ?? "", "base64");    return version === "v1" && given.length === expected.length && timingSafeEqual(given, expected);  });  if (!valid) throw new Error("Invalid signature");  return JSON.parse(body);} app.post("/webhooks/fileverity", express.raw({ type: "application/json" }), (req, res) => {  try {    const event = verify(process.env.FILEVERITY_WEBHOOK_SECRET, req.headers, req.body.toString("utf8"));    handle(event);    res.sendStatus(200);  } catch {    res.sendStatus(400);  }});

Verify against the exact bytes that were sent. Parsing and re-serializing the JSON changes them and breaks the signature.

Delivery and retries

  • Respond with any 2xx status within 10 seconds. Redirects are not followed and count as failures.
  • Anything else, including a timeout or a connection error, schedules a retry with growing delays: 5, 10, 20, 40, 80, 160, 320 and 640 minutes, then every 12 hours. Retries stop 3 days after the event. Retries run on a schedule, so a retry can come later than its delay, never sooner.
  • Delivery is at least once and order is not guaranteed. Use webhook-id to ignore duplicates, and fetch the analysis if you need the latest state.
  • The Console lists the latest 50 deliveries with their status and errors, keeps them for 30 days and lets you resend one. Open a delivery to see its payload, its response time and the first 2 KB of your endpoint's response, so do not return secrets or personal data in that response. A resend keeps the original event ID.
  • Requests carry the user agent Fileverity-Webhooks/1.0.

Automatic disabling

An endpoint that fails continuously for 3 days is disabled. It then receives no new events, and its undelivered events are marked failed. A successful delivery before that clears the failure. Workspace owners and admins are emailed when an endpoint is disabled. Turn the endpoint back on in the Console after you fix it.

The test event

Use Send test on an endpoint in the Console to deliver a sample event right away. It is a signed analysis.completed event with data.test set to true and no report, so you can check your URL and your signature code before real events arrive.

{  "id": "evt_Qm4vB7nP1sK8tL3wR6yZ2cXa",  "object": "event",  "type": "analysis.completed",  "created": 1793700500,  "data": {    "id": "an_Pw3mY6zB8Hk2pQ4vN9sL1xR7",    "object": "analysis",    "created": 1793700120,    "model": "fvimg-2-2026-10-30",    "livemode": true,    "status": "succeeded",    "file": {      "name": "claim-photo.jpg",      "bytes": 2481920,      "format": "jpeg"    },    "report_id": null,    "report_url": null,    "summary": null,    "result": null,    "error": null,    "progress": null,    "usage": {      "amount": 199,      "currency": "usd",      "funding": "balance"    },    "metadata": {      "order_id": "A-10492"    },    "completed_at": 1793700131,    "expires_at": 1794304931,    "test": true  }}

Related: Analyses, Idempotency and Errors.