Aan de slag

Webhooks

Met webhooks verwittigt ProFactuur je eigen systeem zodra er iets gebeurt. Zo hoef je niet telkens zelf op te vragen of er iets veranderd is.

Endpoint toevoegenLink naar deze sectie

  1. Ga naar Instellingen, API en klik bij Webhooks een https-URL aan.
  2. Kies Live of Test en vink de gebeurtenissen aan die je wil ontvangen.
  3. Kopieer het geheim (begint met whsec_). Je ziet het maar één keer. Kwijt? Klik Geheim vernieuwen.
Webhooks zijn beschikbaar in het Team-plan. Een test-endpoint krijgt enkel gebeurtenissen van testdata, een live-endpoint enkel van echte gegevens.

GebeurtenissenLink naar deze sectie

TypeWanneer
factuur.definitiefEen factuur kreeg een nummer
factuur.verzondenEen factuur werd verstuurd via Peppol of e-mail
factuur.betaaldEen factuur werd als betaald gemarkeerd
factuur.peppol_afgeleverdPeppol bevestigt de aflevering
factuur.peppol_geweigerdDe ontvanger weigerde de factuur via Peppol
factuur.peppol_foutVerzending via Peppol is mislukt
creditnota.definitiefEen creditnota kreeg een nummer
creditnota.verzondenEen creditnota werd verstuurd
inbox.ontvangenEr kwam een document binnen via Peppol
offerte.geaccepteerdEen offerte werd geaccepteerd
offerte.geweigerdEen offerte werd geweigerd
klant.aangemaaktEr werd een klant toegevoegd
klant.bijgewerktEen klant werd gewijzigd

PayloadLink naar deze sectie

Elke verzending is een POST met een JSON-body. id is uniek per gebeurtenis: gebruik het om dubbele verwerking te vermijden. data bevat het object zoals de API het teruggeeft.

json
{
  "id": "evt_4b1d...",
  "type": "factuur.betaald",
  "aangemaakt_op": "2026-10-11T09:30:00Z",
  "test": false,
  "data": {
    "id": "9a2b...",
    "type": "factuur",
    "nummer": "2026-0042",
    "status": "betaald",
    "totaal_incl": 726
  }
}

Bij factuur.verzonden en creditnota.verzonden staat in data ook kanaal: peppol of email.

Handtekening controlerenLink naar deze sectie

Elke verzending draagt de header ProFactuur-Signature: t=<timestamp>,v1=<handtekening>. De handtekening is een HMAC-SHA256 met je geheim over de tekst <timestamp>.<ruwe body>, als hex.

  1. Lees t en v1 uit de header.
  2. Bereken HMAC-SHA256 over t + . + de ruwe body, met je geheim.
  3. Vergelijk met v1 in constante tijd.
  4. Weiger als t meer dan 5 minuten afwijkt van nu. Zo kan niemand een oude verzending opnieuw afspelen.
javascript
import crypto from "node:crypto";

// Gebruik de ruwe body, niet een opnieuw opgebouwd JSON-object.
export function verify(rawBody, header, secret, toleranceSec = 300) {
  const parts = Object.fromEntries(header.split(",").map((p) => p.split("=")));
  const t = Number(parts.t);
  if (!t || Math.abs(Date.now() / 1000 - t) > toleranceSec) return false;
  const expected = crypto.createHmac("sha256", secret).update(`${t}.${rawBody}`).digest("hex");
  return parts.v1?.length === expected.length &&
    crypto.timingSafeEqual(Buffer.from(parts.v1), Buffer.from(expected));
}
python
import hmac, hashlib, time

def verify(raw_body: bytes, header: str, secret: str, tolerance: int = 300) -> bool:
    parts = dict(p.split("=", 1) for p in header.split(","))
    t = int(parts.get("t", 0))
    if not t or abs(time.time() - t) > tolerance:
        return False
    expected = hmac.new(secret.encode(), f"{t}.".encode() + raw_body, hashlib.sha256).hexdigest()
    return hmac.compare_digest(expected, parts.get("v1", ""))
php
<?php
function verify(string $rawBody, string $header, string $secret, int $tolerance = 300): bool {
    $parts = [];
    foreach (explode(",", $header) as $p) { [$k, $v] = array_pad(explode("=", $p, 2), 2, ""); $parts[$k] = $v; }
    $t = (int)($parts["t"] ?? 0);
    if (!$t || abs(time() - $t) > $tolerance) return false;
    $expected = hash_hmac("sha256", $t . "." . $rawBody, $secret);
    return hash_equals($expected, $parts["v1"] ?? "");
}
// $ok = verify(file_get_contents("php://input"), $_SERVER["HTTP_PROFACTUUR_SIGNATURE"], $secret);

Antwoorden en nieuwe pogingenLink naar deze sectie

Antwoord binnen 10 seconden met een 2xx-statuscode. Alles anders telt als mislukt. Doe zware verwerking dus best na je antwoord.

PogingWachttijd na vorige
1meteen
21 minuut
35 minuten
430 minuten
52 uur
66 uur
724 uur

Mislukt ook de laatste poging, dan blijft het endpoint actief en krijgt de eigenaar van de onderneming een melding. Je kan elke verzending later zelf opnieuw versturen.

Logboek en testenLink naar deze sectie

  • Per endpoint zie je de laatste 100 verzendingen met tijd, gebeurtenis, statuscode, aantal pogingen en de payload.
  • Met Opnieuw versturen stuur je een verzending nog eens, met een nieuw id.
  • Met Testgebeurtenis sturen krijg je een gebeurtenis van het type webhook.test.

Volgorde en dubbelsLink naar deze sectie

Gebeurtenissen kunnen in een andere volgorde of meer dan eens toekomen. Bewaar de id's die je al verwerkte en haal bij twijfel het object op via de API.