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.
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.
{
"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.
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.