Webhooks

EMPEDIAcloud ruft Ihre URL auf, sobald etwas passiert — ein Audit ist fertig, eine Note verschlechtert sich oder ein Monitor schlägt Alarm. Jede Zustellung ist signiert, damit Sie sicher sein können, dass sie von uns kommt.

Einrichten

Unter API-Zugang → Webhooks einen Webhook anlegen: HTTPS-URL eintragen und Ereignisse wählen (ohne Auswahl erhalten Sie alle). Sie erhalten ein Signatur-Geheimnis (whsec_…), das nur einmal angezeigt wird. Mit „Test“ schicken Sie ein ping-Ereignis, im „Protokoll“ sehen Sie alle Zustellungen. Es sind höchstens 10 Webhooks je Organisation möglich; interne oder nicht öffentlich erreichbare Adressen werden abgelehnt.

Ereignisse

TypAuslöser
scan.completedEin Website-Audit wurde abgeschlossen
scan.failedEin Audit ist fehlgeschlagen
score.degradedDie Note einer Domain ist gegenüber dem letzten Audit um mindestens 5 Punkte oder eine Notenstufe gefallen
monitor.down / monitor.upEin Monitor meldet eine Störung bzw. ist wieder in Ordnung
ssl.expiringEin SSL-Zertifikat läuft bald ab
blacklist.listedDomain oder IP steht auf einer Spam-Liste
heartbeat.missedEin erwarteter Heartbeat ist ausgeblieben
captcha.attack_detectedEMPEDIAcaptcha erkennt ungewöhnlich viele abgelehnte Anfragen für eine Domain (höchstens alle 6 Stunden je Domain)
captcha.quota_80 / captcha.quota_100EMPEDIAcaptcha: 80 % bzw. 100 % der im Paket enthaltenen Prüfungen des Monats verbraucht
handover.driftEine übergebene Website weicht vom dokumentierten Übergabe-Stand ab (neue Befunde „mittel“/„hoch“ oder geänderte Plugins/Themes/Core)
domain.order.completedEine Domain-Bestellung (Registrierung, Verlängerung, Transfer, kostenpflichtige Änderung) wurde ausgeführt
domain.order.failedEine Domain-Bestellung ist fehlgeschlagen; der Betrag wurde zurückgebucht
certificate.issuedEin bestelltes Zertifikat wurde ausgestellt
certificate.failedEine Zertifikatsbestellung ist fehlgeschlagen; der Betrag wurde zurückgebucht
certificate.expiringEin Zertifikat läuft bald ab (30, 14, 7 und 1 Tag vorher)

Aufbau einer Zustellung

POST /ihr/endpunkt HTTP/1.1
Content-Type: application/json
User-Agent: auditcollector-webhooks/1
X-UA-Event: scan.completed
X-UA-Delivery: 7d1f…            (eindeutige ID dieser Zustellung)
X-UA-Signature: t=1760000000,v1=5b3c…

{
  "id": "7d1f…",
  "type": "scan.completed",
  "created": "2026-10-02T10:00:00.000Z",
  "data": {
    "scan_id": "550e8400-e29b-41d4-a716-446655440001",
    "type": "scan",
    "target": "https://example.com",
    "score": 82, "grade": "B",
    "findings": { "total": 14, "high": 1, "medium": 5, "low": 8 },
    "report_url": "__APP_DOMAIN__/scans-detail.html?id=550e8400-…"
  }
}

Bei score.degraded enthält data zusätzlich previous (Note des vorherigen Audits) und delta; bei Monitor-Ereignissen steht in data.monitor der betroffene Monitor und in data.message der Grund.

Signatur prüfen

Die Signatur ist ein HMAC-SHA256 über "<t>.<roher Body>" mit Ihrem Geheimnis. Prüfen Sie sie immer gegen den unveränderten rohen Body (nicht gegen erneut serialisiertes JSON) und lehne zu alte Zeitstempel ab (z. B. älter als 5 Minuten), damit abgefangene Zustellungen nicht wiederholt werden können.

// Node.js (Express: express.raw({ type: 'application/json' }))
import { createHmac, timingSafeEqual } from 'crypto';

function verify(rawBody, header, secret) {
  const parts = Object.fromEntries(header.split(',').map(p => p.split('=')));
  if (Math.abs(Date.now() / 1000 - Number(parts.t)) > 300) return false;
  const expected = createHmac('sha256', secret).update(`${parts.t}.${rawBody}`).digest('hex');
  return expected.length === parts.v1.length && timingSafeEqual(Buffer.from(expected), Buffer.from(parts.v1));
}
# Python
import hmac, hashlib, time

def verify(raw_body: bytes, header: str, secret: str) -> bool:
    parts = dict(p.split("=", 1) for p in header.split(","))
    if abs(time.time() - int(parts["t"])) > 300:
        return False
    expected = hmac.new(secret.encode(), f"{parts['t']}.".encode() + raw_body, hashlib.sha256).hexdigest()
    return hmac.compare_digest(expected, parts["v1"])
// PHP
function verify(string $rawBody, string $header, string $secret): bool {
    parse_str(str_replace(',', '&', $header), $p);
    if (abs(time() - (int)$p['t']) > 300) return false;
    return hash_equals(hash_hmac('sha256', $p['t'] . '.' . $rawBody, $secret), $p['v1']);
}

Zuverlässigkeit

  • Antworte schnell mit 2xx (Timeout 10 Sekunden) und verarbeite die Daten danach. Weiterleitungen (3xx) werden nicht verfolgt.
  • Wiederholungen: Schlägt eine Zustellung fehl, versuchen wir es nach 1 Minute, 5 Minuten, 30 Minuten, 2 Stunden und 12 Stunden erneut (insgesamt 6 Versuche).
  • Doppelte Zustellungen sind möglich. Verarbeiten Sie jede id nur einmal (Idempotenz).
  • Reihenfolge ist nicht garantiert; nutze created bzw. die Zeitangaben in data.
  • Nach 25 fehlgeschlagenen Zustellungen in Folge wird der Webhook deaktiviert; Sie können ihn danach unter API-Zugang wieder aktivieren.
  • Das Geheimnis können Sie jederzeit erneuern; das alte gilt sofort nicht mehr.

Weiter