Skip to content

Webhooks

Register HTTPS endpoints in Settings → Integrations → Public API → Webhooks (or via API when webhooks:manage is granted).

Appraiser Flow sends JSON POSTs when subscribed events occur. Delivery is at-least-once — make handlers idempotent.

MVP events

EventWhen
quote.createdOpen Quote created (API, website, or other intake)
quote.updatedMaterial public fields changed
quote.sentQuote sent to client
quote.acceptedQuote accepted
quote.expiredQuote expired

Later waves add order.*, inspection.*, invoice.*, payment.*.

Headers

http
Content-Type: application/json
X-AF-Event: quote.created
X-AF-Delivery-Id: uuid
X-AF-Timestamp: 1724080000
X-AF-Signature: t=1724080000,v1=hexhmac

Verify signature (required)

Compute HMAC-SHA256 over "{timestamp}.{raw_body}" using your endpoint signing secret. Compare to v1 using a constant-time equals.

Node.js

js
import crypto from "node:crypto";

function verify(sigHeader, rawBody, secret, maxSkewSec = 300) {
  const parts = Object.fromEntries(
    sigHeader.split(",").map((p) => p.trim().split("=")),
  );
  const t = parts.t;
  const v1 = parts.v1;
  if (!t || !v1) return false;
  if (Math.abs(Date.now() / 1000 - Number(t)) > maxSkewSec) return false;
  const expected = crypto
    .createHmac("sha256", secret)
    .update(`${t}.${rawBody}`)
    .digest("hex");
  return crypto.timingSafeEqual(Buffer.from(v1), Buffer.from(expected));
}

Python

python
import hmac
import hashlib
import time

def verify(sig_header: str, raw_body: bytes, secret: str, max_skew: int = 300) -> bool:
    parts = dict(p.split("=", 1) for p in sig_header.split(","))
    t, v1 = parts.get("t"), parts.get("v1")
    if not t or not v1:
        return False
    if abs(time.time() - int(t)) > max_skew:
        return False
    expected = hmac.new(secret.encode(), f"{t}.".encode() + raw_body, hashlib.sha256).hexdigest()
    return hmac.compare_digest(expected, v1)

Respond 2xx quickly. Do heavy work async. Non-2xx triggers retries with backoff.

Payload shape (illustrative)

json
{
  "id": "evt_…",
  "type": "quote.created",
  "created_at": "2026-08-19T18:00:00Z",
  "data": {
    "quote_id": "uuid",
    "status": "open",
    "external_reference": "WEB-10482"
  }
}

Security

  • HTTPS only in staging/production
  • Signing secret is shown once when you create or rotate the endpoint (store it like an API key)
  • Rotate signing secrets if leaked
  • Event data is minimized (ids, status, external_reference) — not a full PII dump
  • Ignore events you do not understand (forward-compatible)
  • Private/link-local webhook URLs are rejected; redirects are not followed
  • Use Send test in Integrations after registering a URL

Authoritative controls: public-api-spec.md §5.5.

API base for Try-it: https://staging-api.appraiserflow.ai