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
| Typ | Auslöser |
|---|---|
scan.completed | Ein Website-Audit wurde abgeschlossen |
scan.failed | Ein Audit ist fehlgeschlagen |
score.degraded | Die Note einer Domain ist gegenüber dem letzten Audit um mindestens 5 Punkte oder eine Notenstufe gefallen |
monitor.down / monitor.up | Ein Monitor meldet eine Störung bzw. ist wieder in Ordnung |
ssl.expiring | Ein SSL-Zertifikat läuft bald ab |
blacklist.listed | Domain oder IP steht auf einer Spam-Liste |
heartbeat.missed | Ein erwarteter Heartbeat ist ausgeblieben |
captcha.attack_detected | EMPEDIAcaptcha erkennt ungewöhnlich viele abgelehnte Anfragen für eine Domain (höchstens alle 6 Stunden je Domain) |
captcha.quota_80 / captcha.quota_100 | EMPEDIAcaptcha: 80 % bzw. 100 % der im Paket enthaltenen Prüfungen des Monats verbraucht |
handover.drift | Eine übergebene Website weicht vom dokumentierten Übergabe-Stand ab (neue Befunde „mittel“/„hoch“ oder geänderte Plugins/Themes/Core) |
domain.order.completed | Eine Domain-Bestellung (Registrierung, Verlängerung, Transfer, kostenpflichtige Änderung) wurde ausgeführt |
domain.order.failed | Eine Domain-Bestellung ist fehlgeschlagen; der Betrag wurde zurückgebucht |
certificate.issued | Ein bestelltes Zertifikat wurde ausgestellt |
certificate.failed | Eine Zertifikatsbestellung ist fehlgeschlagen; der Betrag wurde zurückgebucht |
certificate.expiring | Ein 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
idnur einmal (Idempotenz). - Reihenfolge ist nicht garantiert; nutze
createdbzw. die Zeitangaben indata. - 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.