FixControl/Documentation

User Guide

Webhooks

Subscribe an external system to FixControl events. HMAC-signed, retried, and tenant-scoped.

Outbound webhooks let you push FixControl events into your own systems — a Slack notifier, a billing service, a status page, an internal dashboard. You pick the events, you provide a URL, FixControl signs every delivery and retries it on failure.

Creating a webhook

  1. Open Settings → Integrations → Outbound webhooks.
  2. Click Add webhook.
  3. Fill in:

- URLhttps://.... Must be HTTPS in production. - Events — pick which events to deliver (see below). An empty list means all events for your tenant. - Description — for your own bookkeeping.

  1. Click Create. The signing secret is shown once — copy it immediately into your secret store. You won't be able to read it back later, only rotate it.

Events

Each delivery's body is JSON with an event field. The events that exist today:

EventFired when
issue.createdA new issue is created (intake or manual).
issue.updatedAn issue's status, kind, priority, or assignment changes.
patch.readyA patch passed review and is approved — ready to apply.
patch.approvedAn approved patch was applied; the change landed.
patch.failedA patch was rejected or sent back with requested changes.
pr.createdA pull or merge request was opened for a patch.
integration.errorAn outbound integration call failed (for example, opening a PR).

Subscribe to only the events you care about — narrower filters mean fewer deliveries to handle.

Payload shape

{
  "event": "patch.approved",
  "deliveryId": "dlv_01HV9F...",
  "timestamp": 1746267262,
  "data": {
    "patchId": "ptch_01HV...",
    "workspaceId": "ws_01HU...",
    "version": 2,
    "status": "applied",
    "issueKey": "BIZ-29"
  }
}

data shape varies per event but the envelope (event, deliveryId, timestamp, data) is stable. Use deliveryId for idempotency: if you've already processed a delivery with the same deliveryId, ignore the duplicate.

Verifying the signature

Every delivery is signed with HMAC-SHA256 using your webhook's secret.

Headers on each delivery:

  • X-FixControl-Signature: t=<unix-seconds>,v1=<hex> — HMAC over <timestamp>.<raw body>.
  • X-FixControl-Event: <event-name> — convenience copy of the event field.
  • X-FixControl-Delivery: <id> — same as deliveryId in the body; use it to dedupe.
  • X-FixControl-Webhook: <id> — which webhook subscription this delivery belongs to.

To verify:

  1. Read the raw request body — bytes, not parsed JSON.
  2. Parse t and v1 from the signature header. Reject if t is more than 5 minutes off — that's the replay window.
  3. Compute sha256_hex(hmac(secret, "<t>.<body>")) — the timestamp, a dot, then the raw body.
  4. Compare against v1 using a constant-time comparison.

Example (Node.js):

import { createHmac, timingSafeEqual } from "crypto";

function verify(req, secret) {
  const header = req.headers["x-fixcontrol-signature"] ?? "";
  const t   = header.match(/t=(\d+)/)?.[1];
  const sig = header.match(/v1=([0-9a-f]+)/)?.[1];
  if (!t || !sig || Math.abs(Date.now() / 1000 - Number(t)) > 300) return false;
  const expected = createHmac("sha256", secret)
    .update(`${t}.${req.rawBody}`)
    .digest("hex");
  return sig.length === expected.length &&
         timingSafeEqual(Buffer.from(sig), Buffer.from(expected));
}

Reject any request where verification fails. Never trust a webhook based on URL alone — the URL is not a secret.

Retries

If your endpoint returns a non-2xx status (or doesn't respond within 10 seconds), FixControl retries with backoff:

attempt 1   immediate
attempt 2   ~1 second later
attempt 3   ~5 seconds later

After the third attempt, the delivery is marked failed and stops retrying. Failed deliveries appear under Settings → Integrations → Outbound webhooks → [your webhook] → Deliveries and can be replayed manually.

Testing

The webhook detail page has a Send test event button. It dispatches a minimal webhook.test payload to your endpoint so you can verify your verification + handler before any real events fire.

Operational notes

  • Tenant isolation — a webhook only receives events for the tenant it was created in.
  • Pause without deleting — toggle the Active switch to stop deliveries while keeping the row.
  • Rotate the secret — click Rotate secret. The new secret is shown once; the old one keeps signing in-flight deliveries until the rotation grace period (60 seconds) ends.
  • Delete — soft-delete; deliveries already queued may still attempt up to their retry budget, but the webhook is removed from the UI immediately.

FAQ

Can I receive webhooks from Slack/GitHub through FixControl? That's inbound webhooks. They're configured per-integration on the admin side — see the admin webhooks guide.

My endpoint is behind auth. How do I let FixControl through? We don't support outbound auth headers (Basic, bearer) yet — verify the HMAC signature instead. If you need the request to traverse a private network, run a public proxy that forwards verified requests inward.

What format is the signature in? Hex-encoded HMAC-SHA256 over <timestamp>.<raw request bytes>, delivered as t=<unix-seconds>,v1=<hex> in a single header. The scheme matches Stripe's webhook signatures, so a Stripe-style verifier ports directly.

Anything unclear or wrong?Let us know →

FixControl is a trade name of FixControl B.V. i.o.