Webhooks empfangen und prüfen

Aufbau einer Webhook-Nachricht, Header, Prüfung der Signatur mit Beispielen für PHP, Node.js und Python, Wiederholungen, Duplikate und Reihenfolge.

Stand:

Ein Webhook ist eine HTTPS-Adresse im Fremdsystem, an die Revisio bei jedem abonnierten Ereignis eine Nachricht schickt. Eingerichtet wird er unter Verwaltung → API & Webhooks (Anleitung). Je Organisation sind bis zu zehn Webhooks möglich. Welche Ereignisse es gibt, steht unter Webhook-Ereignisse.

Aufbau einer Nachricht#

Revisio sendet POST mit JSON-Inhalt:

POST /hooks/revisio HTTP/1.1
Host: erp.example.com
Content-Type: application/json
User-Agent: Revisio-Webhooks/0.91.0
X-Revisio-Event: order.completed
X-Revisio-Delivery: 01k7e2g4h6j8k0m2n4p6q8r0s2
X-Revisio-Timestamp: 1793689325
X-Revisio-Signature: t=1793689325,v1=5b0e1c3f7a9d2e4b6c8a0f1e3d5c7b9a2e4f6a8c0b1d3e5f7a9c2b4d6e8f0a1c

{
  "id": "01k7e1f3g5h7j9k1m3n5p7q9r1",
  "event": "order.completed",
  "created_at": "2026-11-03T10:22:05+00:00",
  "test": false,
  "data": {
    "order": {
      "id": "01k7b2c4d6e8f0g2h4j6k8m0n2",
      "number": "A-2026-00012",
      "status": "completed"
    }
  }
}
Teil Bedeutung
id Kennung des Ereignisses; bleibt bei jeder Wiederholung gleich und dient zum Erkennen von Duplikaten
event Name des Ereignisses, gleich dem Header X-Revisio-Event
created_at Zeitpunkt, zu dem die Nachricht erstellt wurde (UTC)
test true nur bei Test senden
data Inhalt je Ereignis
X-Revisio-Delivery Kennung der Zustellung; dieselbe über alle automatischen Versuche, neu bei Erneut senden
X-Revisio-Timestamp Unix-Zeit der Signatur, gleich dem Wert t in X-Revisio-Signature

Nachrichten enthalten nur Kennungen, Nummern, Status, Zeitpunkte und Feldnamen, nie Namen, Adressen oder Texte. Einzelheiten lädt das Fremdsystem über die API. So liegen im Zustellprotokoll und in den Logs des Empfängers keine Kundendaten.

Signatur prüfen#

Jede Nachricht ist mit dem Signaturgeheimnis des Webhooks signiert (whsec_…, wird beim Anlegen einmal angezeigt):

X-Revisio-Signature: t=<Unix-Zeit>,v1=<Hex>
v1 = HMAC-SHA256(Schlüssel = Signaturgeheimnis, Nachricht = "<t>.<Rohtext des Inhalts>")

Das Geheimnis wird vollständig verwendet, einschließlich whsec_. Prüfen Sie so:

  1. Header mit ^t=(\d+),v1=([a-f0-9]{64})$ zerlegen.
  2. Nachrichten verwerfen, deren t mehr als 5 Minuten von der eigenen Uhrzeit abweicht. Das verhindert, dass abgefangene Nachrichten später erneut eingespielt werden.
  3. HMAC über t, einen Punkt und den unveränderten Rohtext berechnen. Nicht das bereits geparste und neu serialisierte JSON verwenden, sonst stimmt die Signatur nicht.
  4. Mit dem Wert v1 zeitkonstant vergleichen.

PHP#

<?php

$secret = getenv('REVISIO_WEBHOOK_SECRET');
$body = file_get_contents('php://input');
$header = $_SERVER['HTTP_X_REVISIO_SIGNATURE'] ?? '';

if (! preg_match('/^t=(\d+),v1=([a-f0-9]{64})$/', $header, $m) || abs(time() - (int) $m[1]) > 300) {
    http_response_code(400);
    exit;
}

if (! hash_equals(hash_hmac('sha256', $m[1].'.'.$body, $secret), $m[2])) {
    http_response_code(401);
    exit;
}

$event = json_decode($body, true, flags: JSON_THROW_ON_ERROR);
store_for_processing($event);
http_response_code(204);

Node.js (Express)#

import crypto from 'node:crypto';
import express from 'express';

const app = express();
const secret = process.env.REVISIO_WEBHOOK_SECRET;

app.post('/hooks/revisio', express.raw({ type: 'application/json' }), (req, res) => {
    const match = /^t=(\d+),v1=([a-f0-9]{64})$/.exec(req.get('X-Revisio-Signature') ?? '');

    if (!match || Math.abs(Date.now() / 1000 - Number(match[1])) > 300) {
        return res.sendStatus(400);
    }

    const expected = crypto.createHmac('sha256', secret).update(`${match[1]}.`).update(req.body).digest();

    if (!crypto.timingSafeEqual(expected, Buffer.from(match[2], 'hex'))) {
        return res.sendStatus(401);
    }

    storeForProcessing(JSON.parse(req.body.toString('utf8')));
    res.sendStatus(204);
});

Python (Flask)#

import hashlib
import hmac
import json
import os
import re
import time

from flask import Flask, abort, request

app = Flask(__name__)
SECRET = os.environ["REVISIO_WEBHOOK_SECRET"].encode()
SIGNATURE = re.compile(r"^t=(\d+),v1=([a-f0-9]{64})$")


@app.post("/hooks/revisio")
def revisio_webhook():
    match = SIGNATURE.match(request.headers.get("X-Revisio-Signature", ""))
    if not match or abs(time.time() - int(match[1])) > 300:
        abort(400)

    body = request.get_data()
    expected = hmac.new(SECRET, match[1].encode() + b"." + body, hashlib.sha256).hexdigest()
    if not hmac.compare_digest(expected, match[2]):
        abort(401)

    store_for_processing(json.loads(body))
    return "", 204

store_for_processing steht jeweils für die eigene Weiterverarbeitung, etwa das Schreiben in eine Warteschlange.

Signatur lokal nachstellen#

Zum Testen des eigenen Empfängers ohne Revisio:

SECRET='whsec_IhrGeheimnis'
BODY='{"id":"01k7e1f3g5h7j9k1m3n5p7q9r1","event":"webhook.test","created_at":"2026-11-03T10:22:05+00:00","test":true,"data":{}}'
T=$(date +%s)
SIG=$(printf '%s.%s' "$T" "$BODY" | openssl dgst -sha256 -hmac "$SECRET" | sed 's/^.*= //')

curl -X POST "https://erp.example.com/hooks/revisio" \
  -H "Content-Type: application/json" \
  -H "X-Revisio-Event: webhook.test" \
  -H "X-Revisio-Signature: t=$T,v1=$SIG" \
  --data-raw "$BODY"

Antworten und Wiederholungen#

  • Jede Antwort mit Status 2xx gilt als zugestellt. Der Inhalt der Antwort ist beliebig; die ersten 2 KB stehen im Zustellprotokoll.
  • Revisio wartet höchstens 10 Sekunden auf die Antwort (Verbindungsaufbau 5 Sekunden). Nehmen Sie die Nachricht an, speichern Sie sie und antworten Sie sofort; die eigentliche Verarbeitung gehört in einen Hintergrundprozess.
  • Weiterleitungen (3xx) werden nicht verfolgt und gelten als Fehler. Tragen Sie die endgültige Adresse ein.
  • Bei Fehlern, Zeitüberschreitung oder Status außerhalb von 2xx versucht Revisio es erneut, insgesamt bis zu fünfmal:
Versuch Abstand zum vorigen
1 sofort
2 1 Minute
3 5 Minuten
4 30 Minuten
5 2 Stunden
  • Nach dem fünften Fehlversuch ist die Zustellung fehlgeschlagen. Im Zustellprotokoll lässt sie sich mit Erneut senden wiederholen, mit derselben Ereignis-id.
  • Nach fünf fehlgeschlagenen Zustellungen in Folge schaltet Revisio den Webhook ab; ausstehende Zustellungen werden nicht mehr gesendet. Eine erfolgreiche Zustellung setzt den Zähler zurück.
  • Test senden schickt genau einen Versuch, auch an einen pausierten Webhook, und zählt nicht als Fehlschlag.

Empfang robust bauen#

Duplikate erkennen. Dieselbe Nachricht kann mehr als einmal ankommen, etwa wenn die Antwort des Empfängers verloren geht oder jemand Erneut senden klickt. Speichern Sie die id verarbeiteter Ereignisse und überspringen Sie bekannte.

Reihenfolge nicht voraussetzen. Nachrichten werden parallel zugestellt und bei Fehlern später wiederholt. Ein order.status_changed kann vor dem zugehörigen order.created eintreffen. Laden Sie im Zweifel den aktuellen Stand über die API und vergleichen Sie updated_at.

Unbekanntes ignorieren. Neue Ereignisse und Felder kommen hinzu, ohne dass sich die Version ändert. Beantworten Sie unbekannte Ereignisse mit 2xx und ignorieren Sie unbekannte Felder.

Echtheit über die Signatur prüfen, nicht über die IP-Adresse des Absenders. Die Adressen der Server können sich ändern.

Testnachrichten erkennen. Bei Test senden ist event webhook.test und test true. data enthält dann nur einen Hinweistext und die Kennung des Webhooks.

Anforderungen an die Empfänger-Adresse#

  • nur https://, mit gültigem Zertifikat
  • öffentlich erreichbar; Adressen im eigenen Netz (localhost, 10.x, 172.16–31.x, 192.168.x, *.local, *.internal usw.) lehnt Revisio ab, auch wenn ein öffentlicher Name auf eine solche Adresse zeigt
  • keine Zugangsdaten in der Adresse (https://benutzer:passwort@…); zur zusätzlichen Absicherung genügt die Signatur oder ein schwer zu ratender Pfad

Revisio prüft die Adresse beim Speichern und vor jeder Zustellung erneut.

Fehlersuche#

Im Zustellprotokoll Ursache
Empfänger antwortete mit HTTP 401/403 Signatur- oder Zugriffsprüfung des Empfängers schlägt fehl, Geheimnis vergleichen
Empfänger antwortete mit HTTP 301/302 Adresse leitet weiter, endgültige Adresse eintragen
Zeitüberschreitung beim Empfänger Antwort dauert länger als 10 Sekunden
TLS-Verbindung fehlgeschlagen Zertifikat abgelaufen, selbst signiert oder Kette unvollständig
Host nicht erreichbar DNS-Name nicht auflösbar
Die Adresse zeigt auf ein internes Netz Ziel ist nicht öffentlich erreichbar
Nicht gesendet: Endpunkt ist deaktiviert Webhook pausiert oder automatisch abgeschaltet

Nach Geheimnis erneuern gilt nur noch das neue Geheimnis. Tragen Sie es im Empfänger ein, bevor das nächste Ereignis kommt, oder pausieren Sie den Webhook kurz.

Schnittstelle (API und Webhooks)

  • API und Webhooks im Überblick

    Was die Schnittstelle kann, wie ein Fremdsystem angebunden wird und wo die technischen Einzelheiten stehen.

  • Grundlagen: Anmeldung, Formate, Blättern, Fehler

    Anmeldung mit Token, Berechtigungen, Datenformate, Blättern mit Cursor, Abgleich mit updated_since, Rate-Limit und Fehlercodes der API.

  • API: Stammdaten

    Kunden, Objekte, Anlagen mit SW-Nummer und Auftragstypen über die API lesen, mit Filtern und allen Feldern.

  • API: Aufträge

    Aufträge über die API abfragen, anlegen und ihren Status ändern, mit Filtern, Feldern, erlaubten Statuswechseln und Beispielen.

  • API: Berichte

    Prüfberichte über die API abfragen und das PDF über einen zeitlich begrenzten, signierten Link laden, zum Beispiel für ERP oder Dokumentenablage.

  • API: Störungen

    Störungen über die API abfragen und melden, mit Statuswerten, Prioritäten, Störungskategorien und Reaktionszeit.

  • Webhooks empfangen und prüfen

    Aufbau einer Webhook-Nachricht, Header, Prüfung der Signatur mit Beispielen für PHP, Node.js und Python, Wiederholungen, Duplikate und Reihenfolge.

  • Webhook-Ereignisse

    Alle Ereignisse, die Revisio per Webhook meldet, wann sie ausgelöst werden und welche Daten sie enthalten.

  • Anwendungsbeispiele

    Typische Anbindungen Schritt für Schritt – ERP-Abrechnung, Berichtsablage im DMS, Störungsmeldung aus einer Leitstelle, Stammdatenabgleich und Wartungsplanung im ERP.

Verwandte Artikel

  • API-Zugänge und Webhooks

    Fremdsystemen wie ERP oder Leitstelle Zugriff über die API geben, Berechtigungen und Ablauf festlegen und per Webhook über neue Aufträge, Berichte und Störungen informieren.

  • Webhook-Ereignisse

    Alle Ereignisse, die Revisio per Webhook meldet, wann sie ausgelöst werden und welche Daten sie enthalten.

  • Berichtsempfänger

    Wer den Prüfbericht per E-Mail bekommt und wie Sie je Auftrag zusätzliche Empfänger eintragen.

  • Echtheit eines Berichts prüfen

    Mit dem Prüfcode und dem QR-Code im Bericht prüft jeder ohne Anmeldung, ob ein Bericht gültig und das PDF unverändert ist.