Connecting...
Tags: webhooks automation

Webhooks

Get notified the moment something happens in a folio. Instead of polling the FolioReady API on a schedule, hook your own systems up directly: post to Slack when a client opens a folio, push files into your CRM when they upload them, or kick off a custom workflow when a folio is accepted.

Why use webhooks?

  • Real-time integrations. A webhook fires within seconds of the underlying event — no polling, no delay.
  • Just HTTP. A small JSON POST to your server. Any language, any framework, any host that can listen on a public URL.
  • Three switches, nine events. Configure up to three webhooks (folio events, file events, notification events) and receive the events you care about.

Configure webhooks under Integrations → Webhooks in the manager UI.

The three webhooks

Webhook Events Reference
Folio Event folio.open, folio.review, folio.update, folio.accepted, folio.expired, folio.closed Folio Events
File Event file.added, file.removed File Events
Notification Event notification.sent Notification Events

Common envelope

Every webhook is an HTTP POST to the URL you configured, with a JSON body. The body has an event key plus one or more context objects:

{
  "event": "folio.open",
  "client": { "...": "..." },
  "folio": { "...": "..." }
}

File events also include a folio_section_file object. Notification events include a notification object. See the per-kind reference pages for full payloads.

HTTP request

Property Value
Method POST
Body JSON, UTF-8
Timeout 15 seconds

Headers sent on every request:

Header Value
Accept application/json
Content-Type application/json
User-Agent folioready (support@folioready.com)
folioready-signature timestamp=<unix-seconds>;signature=<hex>

Verifying the signature

Each webhook is signed with HMAC-SHA256 over "<timestamp>:<raw-body>". The result is base16 lowercase and goes in the folioready-signature header alongside the timestamp.

Signing key:

  • If your company has an API key set, that key is used.
  • If no API key is set, the literal string FOLIOREADY is used. This is a deliberate developer-friendly default so you can wire up a receiver and start sending without configuring anything. For production, set an API key on your account so signatures can only be produced by FolioReady.

A minimal Node.js verifier:

import crypto from "node:crypto";

// Verify a FolioReady webhook request.
// secret: your company API key (or "FOLIOREADY" if no API key is set).
// header: the value of the `folioready-signature` request header.
// body:   the raw request body, BEFORE any JSON parsing.
function verifyFolioreadySignature({ secret, header, body }) {
  const parts = Object.fromEntries(
    header.split(";").map((p) => p.split("=").map((s) => s.trim()))
  );
  const { timestamp, signature } = parts;
  if (!timestamp || !signature) return false;

  const expected = crypto
    .createHmac("sha256", secret)
    .update(`${timestamp}:${body}`)
    .digest("hex");

  return crypto.timingSafeEqual(
    Buffer.from(signature, "hex"),
    Buffer.from(expected, "hex")
  );
}
⚠️ Read the raw body

Compute the signature against the raw request body, before your framework parses it as JSON. Re-serialized JSON will not byte-match what FolioReady signed, and verification will fail. In Express, mount express.raw({ type: "application/json" }) on the webhook route.

Retries and failures

If the request fails, FolioReady retries:

  • Maximum attempts: 6
  • Backoff: 1m, 1m, 5m, 10m, 15m, 30m, 1h
  • Success: HTTP 200. Any other status (or a connection error / timeout) counts as a failure and triggers a retry.

After 6 failed attempts, the event is marked failed and discarded. After 5 or more failed events in a row, the webhook is marked suspended and FolioReady stops sending until a successful delivery clears the failure count — set up your receiver, then trigger a fresh event to clear the suspension.

Delivery log

Every webhook delivery attempt is logged. View the log under Integrations → Webhooks → Webhook Events in the manager UI. Each row shows:

  • Type — the event name (e.g. folio.open).
  • Statuspending, sending, retry, complete, failed, or suspended.
  • Created at — when the event fired.