FixControl/Documentatie

Gebruikershandleiding

Webhooks

Abonneer een extern systeem op FixControl-events. HMAC-ondertekend, met retry en tenant-scoping.

Met uitgaande webhooks stuur je FixControl-events naar je eigen systemen: een notifier, een statuspagina, een intern dashboard. Jij kiest de events en geeft een URL op; FixControl ondertekent elke levering en probeert het een paar keer opnieuw als je endpoint hem niet accepteert.

Voordat je begint

  • Rol — je hebt de rol admin of manager in de workspace nodig. Andere rollen kunnen webhooks niet openen of wijzigen.
  • Een endpoint — een URL die POST-requests met een JSON-body accepteert. Gebruik HTTPS in productie. De URL moet vanaf internet bereikbaar zijn: FixControl levert niet af op adressen in privé-netwerkbereiken, en volgt geen redirects.

Een webhook aanmaken

  1. Open Instellingen → Kanalen. Open in de sectie Webhooks & API de kaart Uitgaande webhooks (of ga direct naar /settings/integrations/webhooks).
  2. Klik Webhook toevoegen.
  3. Vul in:

- Naam — voor je eigen administratie, bijvoorbeeld het systeem dat hem ontvangt. - Endpoint-URL — https://.... - Events — alle events staan aangevinkt als het formulier opent. Vink uit wat je niet nodig hebt. Je moet er minstens één houden: zonder event kan het formulier niet worden opgeslagen ("Kies minstens één event-type").

  1. Klik Webhook aanmaken.
  2. Het signing-secret (begint met ws_) wordt één keer getoond. Kopieer het naar je secret store voordat je op Ik heb hem opgeslagen klikt. FixControl kan het daarna niet meer tonen.

Daarna staat de webhook in de lijst met zijn naam, URL, events (all of een aantal zoals 3/7), de eerste tekens van het secret en zijn status in de kolom Actief.

Events

Elke levering heeft een event-veld. Dit zijn alle events die er nu zijn:

EventWordt verstuurd wanneer
issue.createdEr een nieuwe issue is aangemaakt (intake, handmatig of via de API).
issue.updatedEen issue wordt bijgewerkt. Dit gebeurt bij elke wijziging, ook bij een aangepaste titel of tekst, niet alleen bij een statuswissel.
patch.readyEen patch is goedgekeurd en klaar om toe te passen.
patch.approvedEen goedgekeurde patch is toegepast; de wijziging is doorgevoerd.
patch.failedEen patch is afgewezen, of er zijn wijzigingen gevraagd.
pr.createdEr is een pull of merge request geopend voor een patch.
integration.errorHet openen van een pull of merge request voor een patch is mislukt (bijvoorbeeld een authenticatieprobleem bij de Git-provider).

Omdat issue.updated bij elke wijziging wordt verstuurd, kan een endpoint dat erop geabonneerd is in korte tijd meerdere leveringen voor één issue krijgen. Abonneer je alleen op wat je nodig hebt.

Vorm van de payload

Elke levering heeft dezelfde envelope:

{
  "event": "patch.approved",
  "deliveryId": "whd_lzx3k9q2_8f3k2j1a",
  "timestamp": 1746267262,
  "data": {
    "patchId": "ptch_01HV...",
    "workspaceId": "ws_01HU...",
    "version": 2,
    "status": "applied",
    "previousStatus": "approved",
    "decidedBy": "usr_...",
    "issueKey": "BIZ-29"
  }
}
  • event — de naam van het event.
  • deliveryId — een unieke id per levering, in de vorm whd_<tijd>_<willekeurig>. Nieuwe pogingen voor dezelfde levering houden dezelfde id, dus gebruik hem om duplicaten te negeren.
  • timestamp — Unix-seconden op het moment dat deze poging werd verstuurd.
  • data — de eigen velden van het event. Bij issue-events zijn dat de issue-key (zoals BIZ-29), het type, de titel, de prioriteit en de status; bij patch-events de patch-id, versie, status en issue-key; bij pr.created de URL, het nummer en de branch van de pull request. De precieze velden verschillen per event, dus lees de velden die je nodig hebt en negeer wat je niet herkent.

De signature verifiëren

Elke levering wordt ondertekend met HMAC-SHA256 en het secret van je webhook. Elk request heeft deze headers:

  • X-FixControl-Signature: t=<unix-seconden>,v1=<hex> — de signature.
  • X-FixControl-Event — de naam van het event.
  • X-FixControl-Delivery — dezelfde waarde als deliveryId.
  • X-FixControl-Webhook — de id van de webhook waar deze levering bij hoort (geen secret).

De signature is de hex-gecodeerde HMAC-SHA256 van de string <t>.<ruwe body>: de t-waarde uit de header, een punt, en dan de request-body precies zoals ontvangen.

Zo verifieer je:

  1. Lees de ruwe request-body als bytes. Parse en serialiseer de JSON niet eerst opnieuw; dan veranderen de bytes.
  2. Splits de header op , en lees de waarden t en v1.
  3. Wijs het request af als t meer dan 5 minuten van je huidige tijd afwijkt.
  4. Bereken HMAC-SHA256(secret, "<t>.<ruwe body>") als hex.
  5. Vergelijk die met v1 met een constant-time vergelijking.

Voorbeeld (Node.js, Express):

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

const app = express();
const SECRET = process.env.FIXCONTROL_WEBHOOK_SECRET; // ws_...

function verify(rawBody, header, secret) {
  if (!header) return false;
  let t = null;
  let v1 = null;
  for (const part of header.split(",")) {
    const [k, v] = part.trim().split("=", 2);
    if (k === "t") t = Number(v);
    if (k === "v1") v1 = v;
  }
  if (!Number.isFinite(t) || !v1) return false;
  if (Math.abs(Math.floor(Date.now() / 1000) - t) > 300) return false;

  const expected = createHmac("sha256", secret)
    .update(`${t}.${rawBody.toString("utf8")}`)
    .digest("hex");
  if (expected.length !== v1.length) return false;
  return timingSafeEqual(Buffer.from(expected, "hex"), Buffer.from(v1, "hex"));
}

app.post("/fixcontrol-webhook", express.raw({ type: "application/json" }), (req, res) => {
  if (!verify(req.body, req.get("X-FixControl-Signature"), SECRET)) {
    return res.status(401).end();
  }
  const delivery = JSON.parse(req.body.toString("utf8"));
  // Ontdubbel op delivery.deliveryId en verwerk daarna delivery.event.
  res.status(204).end();
});

Voorbeeld (Python):

import hashlib, hmac, time

def verify(raw_body: bytes, header: str, secret: str) -> bool:
    parts = dict(p.strip().split("=", 1) for p in (header or "").split(",") if "=" in p)
    t, v1 = parts.get("t"), parts.get("v1")
    if not t or not v1 or not t.isdigit():
        return False
    if abs(int(time.time()) - int(t)) > 300:
        return False
    expected = hmac.new(secret.encode(), f"{t}.".encode() + raw_body, hashlib.sha256).hexdigest()
    return hmac.compare_digest(expected, v1)

Wijs elk request af waarvan de verificatie mislukt. Vertrouw een webhook nooit alleen op basis van de URL; een URL is geen secret.

Nieuwe pogingen

FixControl telt een levering als geaccepteerd als je endpoint binnen 10 seconden met een 2xx-status antwoordt. Al het andere (een andere status, een redirect, een time-out, een verbindingsfout) is een mislukte poging. Een levering krijgt in totaal 3 pogingen:

poging 1   direct
poging 2   ongeveer 1 seconde later
poging 3   ongeveer 5 seconden later

Zolang er pogingen over zijn, staat de levering op Opnieuw proberen. Na de derde mislukte poging staat hij op Opgegeven en wordt hij niet meer geprobeerd. Er is geen knop om een levering opnieuw af te spelen: heb je een levering gemist, lees dan de actuele stand via de publieke API, of verstuur een test om je endpoint te controleren.

Leveringen controleren

Elke rij in de lijst met webhooks heeft deze acties:

  • Test versturen — stuurt een webhook.test-levering naar je endpoint, ondertekend zoals een echte, en opent het leveringslog. De data van een testlevering bevat "kind": "test". Een uitgeschakelde webhook verstuurt geen tests; schakel hem eerst in.
  • Leveringen bekijken — opent Recente leveringen voor die webhook onder de lijst: event, status (In wachtrij, Opnieuw proberen, Geslaagd, Opgegeven), responscode, aantal pogingen, de fout, de tijd en de request-ID (dezelfde waarde als deliveryId). Met de verversknop werk je het bij.
  • Bewerken — wijzig de naam, URL of events. Het secret blijft hetzelfde.
  • Verwijderen — verwijdert de webhook na een bevestiging.

Pauzeren, verwijderen en het secret vervangen

  • Pauzeren zonder verwijderen — klik Uitschakelen in de kolom Actief. Een uitgeschakelde webhook krijgt geen events meer tot je weer op Inschakelen klikt.
  • Verwijderen — verwijdert de webhook definitief. Leveringen die nog opnieuw geprobeerd werden, eindigen als Opgegeven met de fout webhook_deleted. Het leveringslog van die webhook wordt niet meer in de app getoond.
  • Nieuw secret — een secret kun je niet roteren of opnieuw tonen. Is het gelekt of kwijt, maak dan een nieuwe webhook aan met dezelfde URL en events, zet je endpoint over op het nieuwe secret en verwijder daarna de oude webhook.

Als het niet werkt

Wat je zietWat je doet
Webhook aanmaken blijft grijsVul een naam en URL in en houd minstens één event aangevinkt.
"Kan webhook niet opslaan."Controleer of de URL een volledig http://- of https://-adres is.
Leveringen staan op Opgegeven met http_4xx of http_5xxJe endpoint weigerde of crashte. Kijk in de logs; een 401 betekent meestal dat de signature-controle mislukte (verkeerd secret, of de body werd geparst vóór het verifiëren).
Fout begint met redirect_blocked_Je endpoint antwoordde met een redirect. Gebruik direct de uiteindelijke URL.
Fout begint met url_blocked_De URL verwijst naar een privé- of intern adres. Gebruik een adres dat vanaf internet bereikbaar is.
"Test-levering kon niet worden ingepland."De webhook staat mogelijk uit. Schakel hem in en probeer het opnieuw.

Zie ook

Veelgestelde vragen

Mijn endpoint zit achter authenticatie. Hoe laat ik FixControl door? FixControl stuurt geen eigen authenticatieheaders (Basic, bearer) mee. Verifieer in plaats daarvan de HMAC-signature. Moet het request een privé-netwerk bereiken, zet er dan een publieke proxy voor die de signature controleert en het request naar binnen doorstuurt.

Kan één webhook leveringen voor meerdere workspaces krijgen? Nee. Een webhook ontvangt alleen events van de workspace waarin hij is aangemaakt.

In welk formaat is de signature? Hex-gecodeerde HMAC-SHA256 over <timestamp>.<ruwe request-body>, verstuurd als t=<unix-seconden>,v1=<hex> in één header. Dat is dezelfde vorm die Stripe gebruikt, dus een verifier in Stripe-stijl kun je direct overnemen.

Iets onduidelijk of fout?Laat het ons weten →

FixControl is een handelsnaam van FixControl B.V.