Skip to main content

Documentation Index

Fetch the complete documentation index at: https://developers.flowestate.app/llms.txt

Use this file to discover all available pages before exploring further.

FlowEstate sends webhooks when leads, projects, or units change. Subscribers register a target URL and an event type; FlowEstate fans out signed POST requests with a JSON body. To manage subscriptions programmatically (create, list, delete), see the Webhooks API reference. This page covers what your receiver needs to do once a subscription is in place.

Headers

Every delivery includes:
HeaderDescription
Content-TypeAlways application/json.
User-AgentFlowEstate-Webhook/1.0
X-FlowEstate-EventEvent type, e.g. lead.created.
X-FlowEstate-DeliveryUUID of this delivery attempt. Use it for idempotent processing.
X-FlowEstate-Signaturesha256=<hex> HMAC-SHA256 of the exact raw body using the endpoint secret.
X-FlowEstate-SourceOrigin of the event: ui, api, or system. Use this to break loops — see below.

Body

{
  "id": "evt_<uuid>",
  "type": "lead.created",
  "createdAt": "2026-04-29T18:04:15.000Z",
  "organization": {
    "id": "org_...",
    "name": "Acme Realty",
    "slug": "acme-realty"
  },
  "data": { /* event-specific payload */ }
}
data is event-specific:
  • lead.* events carry data.lead.
  • project.* events carry data.project.
  • unit.* events carry data.unit and data.project.
  • lead.note_added carries data.leadId and data.note.
  • lead.communication_logged carries data.leadId and data.communication.
For the full list of events see Enums.

Verifying the signature

Always verify the signature before trusting a payload. Anyone who knows your URL can hit it; only FlowEstate has the secret.
import crypto from "node:crypto";

function verify(rawBody, signatureHeader, secret) {
  const expected = "sha256=" + crypto
    .createHmac("sha256", secret)
    .update(rawBody)
    .digest("hex");
  const a = Buffer.from(expected);
  const b = Buffer.from(signatureHeader);
  return a.length === b.length && crypto.timingSafeEqual(a, b);
}

// Express
app.post("/webhook", express.raw({ type: "application/json" }), (req, res) => {
  if (!verify(req.body, req.get("X-FlowEstate-Signature"), process.env.FLOWESTATE_SECRET)) {
    return res.status(401).end();
  }
  const event = JSON.parse(req.body.toString("utf8"));
  // ... handle event ...
  res.status(200).end();
});
Sign the raw bytes of the request body. Re-serializing the parsed JSON will produce a different signature because key order, whitespace, and number formatting may differ.
The secret comes from the response of POST /webhooks/subscriptions and is shown only once. If you lose it, delete the subscription and create a new one.

Acknowledging

Return any 2xx HTTP status to acknowledge receipt. FlowEstate doesn’t read the response body — empty 200 is fine. If your handler does heavy work (calling other APIs, writing to a slow database), acknowledge first, process later:
app.post("/webhook", (req, res) => {
  if (!verify(...)) return res.status(401).end();
  res.status(200).end();          // ACK first
  enqueueForBackgroundProcessing(req.body);  // process out-of-band
});
The delivery timeout is a few seconds — slow handlers cause spurious retries.

Retry policy

Failed deliveries (network error, timeout, or HTTP ≥ 400) are retried with this backoff:
AttemptDelay before retry
1— (initial)
21 minute
35 minutes
430 minutes
52 hours
612 hours
After five failed attempts the delivery is marked failed and won’t be retried again. The X-FlowEstate-Delivery header stays the same across retries — use it as your idempotency key when processing.

Preventing loops

When you write to FlowEstate via this REST API, the resulting webhook fans out with X-FlowEstate-Source: api. If your receiver also writes back into FlowEstate, drop events with source api to avoid an infinite loop.
if (req.headers["x-flowestate-source"] === "api") {
  return res.status(200).end();  // ack and ignore
}
The other source values:
  • ui — change made by a user clicking in the FlowEstate dashboard.
  • system — change made by an internal worker (assignments, automations).
  • api — change made through this REST API (your own writes, partner platforms).