FileNest/Docs

Webhooks API

FileNest sends HMAC-signed HTTP POST requests to your registered endpoints whenever a file event occurs in a project.

Events

EventFired when
file.uploadedUpload request received and file record created
file.readyProcessing pipeline completed successfully
file.failedProcessing pipeline failed
file.quarantinedVirus scan flagged the file
file.deletedFile soft-deleted via DELETE /files/{id}

Payload shape

Every event delivery shares the same envelope:

{
  "event": "file.ready",
  "timestamp": "2026-06-21T09:00:00Z",
  "organization_id": "org_01j...",
  "project_id": "proj_01j...",
  "data": {
    "file_id": "file_01j...",
    "filename": "invoice.pdf",
    "size": 204800,
    "mime_type": "application/pdf",
    "status": "ready"
  }
}

Signature verification

Every request carries an x-filenest-signature header — an HMAC-SHA256 hex digest of the raw request body, keyed with your webhook's signing_secret.

import { createHmac, timingSafeEqual } from "crypto";
 
export function verifyWebhookSignature(
  body: string,           // raw request body — do NOT parse before verifying
  signature: string,      // value of x-filenest-signature header
  signingSecret: string,  // from your webhook config
): boolean {
  const expected = createHmac("sha256", signingSecret)
    .update(body, "utf8")
    .digest("hex");
  return timingSafeEqual(Buffer.from(signature), Buffer.from(expected));
}

Always use a constant-time comparison (timingSafeEqual) to prevent timing attacks.

Retry policy

If your endpoint returns a non-2xx status code or times out (timeout: 10 s), FileNest retries with exponential backoff:

AttemptDelay
1immediate
230 s
35 min
41 h
56 h

After 5 failed attempts the event is marked dead and no further retries occur.


List webhooks

GET /v1/projects/{project_id}/webhooks

Required scope: projects:read

Response 200:

{
  "items": [
    {
      "id": "wh_01j...",
      "url": "https://example.com/webhooks/filenest",
      "events": ["file.ready", "file.deleted"],
      "is_active": true,
      "created_at": "2026-06-21T09:00:00Z",
      "updated_at": "2026-06-21T09:00:00Z"
    }
  ],
  "total": 1
}

Create a webhook

POST /v1/projects/{project_id}/webhooks

Required scope: projects:update

curl -X POST https://filenest.drgodly.com/v1/projects/{id}/webhooks \
  -H "Authorization: Bearer fn_live_..." \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://example.com/webhooks/filenest",
    "events": ["file.ready", "file.deleted"],
    "is_active": true
  }'

Response 201:

{
  "id": "wh_01j...",
  "url": "https://example.com/webhooks/filenest",
  "events": ["file.ready", "file.deleted"],
  "is_active": true,
  "signing_secret": "whsec_...",
  "created_at": "2026-06-21T09:00:00Z"
}

Save your signing secret

signing_secret is returned exactly once on creation. Store it securely — it cannot be retrieved again.

Update a webhook

PUT /v1/projects/{project_id}/webhooks/{webhook_id}

Required scope: projects:update

{
  "url": "https://example.com/webhooks/filenest-v2",
  "events": ["file.ready", "file.failed", "file.quarantined"],
  "is_active": true
}

Response 200: updated webhook object (no signing_secret field — secret does not rotate on update).

Delete a webhook

DELETE /v1/projects/{project_id}/webhooks/{webhook_id}

Required scope: projects:update

Response 204: no body. Pending retries are cancelled.

Error codes

StatusCodeMeaning
404webhook_not_foundWebhook does not exist in this project
422invalid_urlEndpoint URL is not a valid HTTPS URL