Webhooks

Register an endpoint, verify the signature, and read the delivery history.

A webhook endpoint receives every domain event the workspace produces, filtered by an event_names list. An empty list means every event. Delivery is at-least-once: a republished event records a second delivery rather than silently dropping one, and you deduplicate on the seq the body carries.

Register an endpoint

POST /api/webhooks returns the endpoint with its signing secret, shown in full exactly once. Store it. The list and update answers never carry the secret again.

sh
curl -X POST http://127.0.0.1/api/webhooks \
  -H 'content-type: application/json' \
  -H 'authorization: Bearer fk_<key>' \
  -d '{"url":"https://example.com/hooks/fikadesk","event_names":["conversation_part.created"],"description":"replies"}'

The payload

The body is the domain event envelope of the ordering spec. seq is the per-tenant event number, so a gap on your side means fetch the change feed from GET /api/events rather than guessing.

Delivery body
{
  "tenant_id": "0f1c1a3e-…",
  "seq": 42,
  "type": "conversation_part.created",
  "aggregate_type": "conversation",
  "aggregate_id": "…",
  "payload": { "part_id": "…", "part_type": "message", "…": "…" },
  "created_at": "2026-08-31T09:41:00Z"
}

Verify the signature

The delivery worker signs the raw body with your endpoint secret and sends the hex HMAC-SHA256 digest under x-fikadesk-signature. There is no timestamp header; replay protection on this direction is the seq dedup. Verify with a constant-time compare.

Signature check
import { createHmac, timingSafeEqual } from "node:crypto";

function verify(rawBody: Buffer, header: string | null, secret: string): boolean {
  if (header === null) return false;
  const expected = createHmac("sha256", secret).update(rawBody).digest();
  const provided = Buffer.from(header, "hex");
  return expected.length === provided.length && timingSafeEqual(expected, provided);
}

// In your request handler, read the raw bytes before parsing JSON.
const ok = verify(rawBody, req.headers["x-fikadesk-signature"], process.env.WEBHOOK_SECRET);

Retries

A non-2xx response is retried up to eight attempts, the delay doubling from thirty seconds each time. After the last attempt the delivery goes dead and stays visible on GET /api/webhooks/{id}/deliveries, with the last status and error.

PUT /api/webhooks/{id} changes the url, filter, enabled flag or description; the secret is immutable. DELETE /api/webhooks/{id} removes the endpoint and its delivery history.