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
- Open Settings → Integrations → Outbound webhooks.
- Click Add webhook.
- Fill in:
- URL — https://.... 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.
- 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:
| Event | Fired when |
|---|---|
issue.created | A new issue is created (intake or manual). |
issue.updated | An issue's status, kind, priority, or assignment changes. |
patch.ready | A patch passed review and is approved — ready to apply. |
patch.approved | An approved patch was applied; the change landed. |
patch.failed | A patch was rejected or sent back with requested changes. |
pr.created | A pull or merge request was opened for a patch. |
integration.error | An 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 asdeliveryIdin the body; use it to dedupe.X-FixControl-Webhook: <id>— which webhook subscription this delivery belongs to.
To verify:
- Read the raw request body — bytes, not parsed JSON.
- Parse
tandv1from the signature header. Reject iftis more than 5 minutes off — that's the replay window. - Compute
sha256_hex(hmac(secret, "<t>.<body>"))— the timestamp, a dot, then the raw body. - Compare against
v1using 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 laterAfter 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.