Integrations · Receive events
Outbound webhooks
Every notification fires an HTTPS POST to your receiver in real time. HMAC-SHA256 signed; verify and act.
Configure
- Per-employee:
/me/agent→ Webhook section → Add Webhook. One signing secret per row. - Tenant-wide config (
/admin/integrations) is not yet shipped. Until it is, admins / hr_admins subscribe to global signals liketeam_support.approvedvia their own per-employee webhook at/me/agent. - Save → page now shows the signing secret. Copy to receiver; rotate from same row if it leaks.
Request shape
POST with these headers + JSON body. 8-second timeout per attempt; 10 consecutive 4xx/5xx auto-disables.
# Use any HTTPS receiver (ngrok / cloudflared / your prod host).
# Configure it once at /me/agent (tenant-wide /admin/integrations is on the roadmap).
# Inbound POST has these headers:
# X-FluxDesk-Signature: v1=<hex-sha256-hmac-of-body>
# X-FluxDesk-Event-Type: task_assigned | review | blocker | ...
# X-FluxDesk-Delivery-Id: <uuid>
#
# Verify HMAC, then act on the body.
curl -X POST https://your-receiver.example.com/fluxdesk-events \
-H 'content-type: application/json' \
-H 'X-FluxDesk-Signature: v1=<hex>' \
-H 'X-FluxDesk-Event-Type: task_assigned' \
-d '{"version":1,"type":"task_assigned","title":"…"}'Verify HMAC (Node / Express)
Recompute HMAC-SHA256(secret, raw_body) and compare in constant time with the value after v1= in X-FluxDesk-Signature. Get the RAW body, not parsed JSON, before verifying.
import crypto from "node:crypto";
import express from "express";
const SECRET = process.env.FLUXDESK_WEBHOOK_SECRET!;
const app = express();
// IMPORTANT: raw body, not parsed JSON, for HMAC.
app.post(
"/fluxdesk-events",
express.raw({ type: "application/json" }),
(req, res) => {
const sig = req.header("x-fluxdesk-signature") ?? "";
const expected =
"v1=" + crypto.createHmac("sha256", SECRET).update(req.body).digest("hex");
const ok =
sig.length === expected.length &&
crypto.timingSafeEqual(Buffer.from(sig), Buffer.from(expected));
if (!ok) return res.status(401).end("invalid signature");
const event = JSON.parse(req.body.toString("utf8"));
console.log("got event:", event.type, event.title);
res.status(204).end();
},
);
app.listen(8787);Event types
Subscribe to all and branch on type:
task_assignedA request was dispatched to the user.
task_statusA request the user is involved in changed status.
blockerA blocker was raised, acknowledged, or resolved.
reviewA request entered the review queue or was returned.
meetingMeeting reminder (10 min before, etc.).
weekly_digestMonday founder digest.
team_support.approvedTeam-support request approved (admin / fan-out hook).
systemCron, GitHub auto-links, membership changes, etc.