FileForms
Webhooks

Setup & Verification

Create webhook endpoints, store the signing secret, and verify the FileForms-Signature header.

Webhooks notify your systems when something happens to your orders: a filing changes status, or a document becomes available. FileForms sends two event types — filing.status_changed and document.uploaded — described in the event catalog.

Create an endpoint

Create endpoints via the API or in the dashboard under Settings → Developers → Webhook endpoints → Add Endpoint.

curl -X POST https://api.fileforms.com/v1/webhook-endpoints \
  -H "x-api-key: sk_live_..." \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://api.example.com/webhooks/fileforms",
    "eventTypes": ["filing.status_changed", "document.uploaded"]
  }'
{
  "id": "we_a1b2c3d4e5f6g7h8",
  "url": "https://api.example.com/webhooks/fileforms",
  "eventTypes": ["filing.status_changed", "document.uploaded"],
  "createdAt": "2026-07-29T12:00:00.000Z",
  "secret": "whsec_..."
}

Endpoint URLs must be https:// and publicly resolvable — URLs that resolve to private or internal addresses are rejected.

The signing secret is returned exactly once

The whsec_ secret appears only in the create response (or the one-time dialog in the dashboard). It cannot be retrieved later, and there is no rotation endpoint — if you lose it or need to rotate, delete the endpoint and create a new one. Note that deleting an endpoint also deletes its delivery history.

Updating an endpoint (PATCH /webhook-endpoints/{id}) replaces eventTypes wholesale rather than merging, and keeps the existing secret.

Verify signatures

Every delivery is a POST with a JSON body and a FileForms-Signature header:

FileForms-Signature: t=1722268800,v1=5257a869e7ecebeda32affa62cdca3fa51cad7e77a0e56ff536d0ce8e108d8bd
  • t — Unix timestamp (seconds) of when the delivery was signed
  • v1 — hex-encoded HMAC-SHA256 of {t}.{raw request body}, keyed with your endpoint's whsec_ secret (the full secret string, prefix included)

Verify every delivery before acting on it:

import crypto from "node:crypto";

function verifyWebhookSignature(
  rawBody: string,
  header: string,
  secret: string,
): boolean {
  const parts = Object.fromEntries(header.split(",").map((p) => p.split("=")));

  const expected = crypto
    .createHmac("sha256", secret)
    .update(`${parts.t}.${rawBody}`)
    .digest("hex");

  const expectedBuf = Buffer.from(expected);
  const actualBuf = Buffer.from(parts.v1 ?? "");

  const isValid =
    expectedBuf.length === actualBuf.length &&
    crypto.timingSafeEqual(expectedBuf, actualBuf);

  const isFresh = Math.abs(Date.now() / 1000 - Number(parts.t)) < 300;

  return isValid && isFresh;
}

Three rules that prevent almost every verification bug:

  1. Sign the raw body bytes. Compute the HMAC over the exact bytes you received — don't parse and re-serialize the JSON. With Express, use express.raw({ type: 'application/json' }) and pass req.body.toString().
  2. Compare in constant time (crypto.timingSafeEqual above).
  3. Reject stale timestamps. Anything older than 5 minutes should be treated as a possible replay.

Respond with any 2xx status quickly — deliveries time out after 30 seconds, and redirects are treated as failures. Do heavy processing asynchronously after acknowledging.

Email notifications for the same events

The same activity also exists as organization-wide email notifications (Settings → Notifications): "Document uploads" and "Filing status changes", both on by default. The toggles control email only — webhook delivery is never affected.

  • API integrations usually want these off once webhooks are live, so your users aren't emailed about events your systems already handle.
  • Dashboard partners without an API integration usually want them on — email is how their users hear about filings and documents.

On this page