Doku-Navigation: WebhooksWann eine Nachricht kommt
Betrieb

Webhooks bei neuen Werten

Ab dem Tarif Pro meldet Quellenkontor jeden neuen oder geänderten Wert an deinen Server. So pflegst du Werte, ohne selbst nachzusehen.

Wann eine Nachricht kommt

#

Webhooks gibt es ab dem Tarif Pro. Sobald ein neuer, geänderter oder korrigierter Wert im Änderungsprotokoll steht, schickt Quellenkontor eine Nachricht an deine Adresse.

  • Zugestellt wird einmal am Tag, um 07:00 Uhr UTC. Eine Änderung vom Montag erreicht dich also spätestens am Dienstagmorgen.
  • Du bekommst nur Änderungen, die nach dem Anlegen des Webhooks eingetragen wurden. Den Stand davor holst du einmal über den Verlauf.
  • Neue Werte tragen wir ein, wenn sie amtlich verkündet sind, meist Wochen vor ihrer Gültigkeit. Deine Software hat damit Vorlauf.

Webhook anlegen

#
  1. Im Konto unter Webhooks die Adresse deines Servers eintragen. Sie muss mit https:// beginnen und einen Domainnamen haben, keine IP-Adresse und keinen internen Namen.
  2. Die Datensätze wählen, die dich interessieren. Ohne Auswahl bekommst du alle.
  3. Das Geheimnis kopieren. Es beginnt mit whsec_ und wird nur einmal angezeigt. Damit prüfst du die Signatur.
  4. Mit "Test senden" eine Testnachricht auslösen und prüfen, dass dein Server sie annimmt.

Je Konto sind bis zu 10 Webhooks möglich.

Ereignisse

#
ereignisBedeutung
wert.neuEine neue Stufe ist eingetragen, zum Beispiel die Rechengrößen des Folgejahres
ankuendigungEine verkündete Änderung, deren Wert oder Feld erst später kommt, zum Beispiel eine neue Formel ab dem nächsten Jahr
wert.geaendertEine bestehende Stufe hat sich durch eine neue Regelung geändert
korrekturWir haben einen Fehler in einem Wert berichtigt. Prüfe betroffene Berechnungen
datensatz.neuEin neuer Datensatz ist verfügbar
testTestnachricht aus dem Konto, ohne Datensatz

Aufbau der Nachricht

#

Die Nachricht kommt per POST mit dem Header Content-Type: application/json:

Nachricht
{
  "ereignis": "wert.neu",
  "id": 42,
  "datensatz": "mindestlohn",
  "gueltig_ab": "2027-01-01",
  "text": "Mindestlohn ab 01.01.2027 eingetragen",
  "beleg_url": "https://www.recht.bund.de/bgbl/1/2025/268/VO.html",
  "abruf": "https://api.quellenkontor.dev/v1/hr/mindestlohn?datum=2027-01-01",
  "gesendet_am": "2026-11-05T07:00:04.000Z"
}
FeldBedeutung
ereignisArt der Änderung, siehe oben
idNummer des Eintrags im Änderungsprotokoll, eindeutig und aufsteigend
datensatzBetroffener Datensatz, bei test null
gueltig_abAb wann der neue Wert gilt, sonst null
textKurze Beschreibung der Änderung
beleg_urlAmtliche Quelle der Änderung
abrufFertige API-Adresse, um den neuen Wert abzufragen
gesendet_amZeitpunkt des Versands in UTC

Signatur prüfen

#

Jede Nachricht trägt den Header Quellenkontor-Signatur in der Form t=<Zeitstempel>,v1=<Signatur>. Die Signatur ist ein HMAC-SHA256 in Hex über den Zeitstempel, einen Punkt und den rohen Text der Anfrage, mit dem Geheimnis des Webhooks als Schlüssel. So prüfst du sie:

  1. Den rohen Text lesen, bevor du ihn als JSON parst. Schon ein anderes Leerzeichen verändert die Signatur.
  2. Die Signatur selbst berechnen und zeitkonstant mit v1 vergleichen.
  3. Nachrichten mit einem Zeitstempel älter als fünf Minuten verwerfen. Das schützt vor wiederholten Nachrichten.

Node.js

JavaScript
import crypto from "node:crypto";

// kopf: Wert des Headers "Quellenkontor-Signatur", body: der rohe Text der Anfrage
export function istEcht(kopf, body, geheimnis) {
  const teile = Object.fromEntries(kopf.split(",").map((t) => t.split("=")));
  const soll = crypto.createHmac("sha256", geheimnis).update(`${teile.t}.${body}`).digest("hex");
  const frisch = Math.abs(Date.now() / 1000 - Number(teile.t)) <= 300;
  return frisch && typeof teile.v1 === "string" && teile.v1.length === soll.length
    && crypto.timingSafeEqual(Buffer.from(soll), Buffer.from(teile.v1));
}

Python

Python
import hashlib
import hmac
import time

def ist_echt(kopf: str, body: bytes, geheimnis: str) -> bool:
    teile = dict(t.split("=", 1) for t in kopf.split(","))
    soll = hmac.new(geheimnis.encode(), teile["t"].encode() + b"." + body, hashlib.sha256).hexdigest()
    frisch = abs(time.time() - int(teile["t"])) <= 300
    return frisch and hmac.compare_digest(soll, teile.get("v1", ""))
Das Geheimnis gehört wie der API-Schlüssel in eine Umgebungsvariable. Hast du es verloren, lösch den Webhook und leg ihn neu an.

Antworten und Wiederholungen

#
  • Antworte innerhalb von 8 Sekunden mit einem Status 2xx. Erledige aufwendige Arbeit danach, zum Beispiel über eine Warteschlange.
  • Weiterleitungen (3xx) folgen wir nicht, sie gelten als Fehlschlag.
  • Bei einem Fehlschlag versuchen wir es beim nächsten täglichen Lauf erneut, insgesamt höchstens fünf Mal. Danach geben wir diese Nachricht auf.

Nachrichten verarbeiten

#

Speichere die id jeder verarbeiteten Nachricht. Wenn dein Server eine Nachricht verarbeitet, aber zu spät geantwortet hat, kommt sie beim nächsten Lauf noch einmal. Mit der id erkennst du das. Den neuen Wert holst du dann über die Adresse in abruf, das zählt als eine Abfrage.

Next.js, Route Handler
// app/api/quellenkontor/route.ts
import { istEcht } from "@/lib/signatur";

export async function POST(request: Request) {
  const body = await request.text(); // roher Text, vor jedem JSON.parse
  const kopf = request.headers.get("quellenkontor-signatur") ?? "";
  if (!istEcht(kopf, body, process.env.QK_WEBHOOK_GEHEIMNIS!)) return new Response(null, { status: 401 });

  const nachricht = JSON.parse(body);
  if (await schonVerarbeitet(nachricht.id)) return new Response(null, { status: 200 });
  await inWarteschlange(nachricht); // eigentliche Arbeit später erledigen
  return new Response(null, { status: 204 });
}

Fehlt etwas oder ist etwas unklar? Schreib uns, wir ergänzen die Doku.