Skip to content

Webhooks ​

Schick jeden Alarm als JSON an deinen eigenen Endpunkt. Ein Webhook ist ein Benachrichtigungskanal wie Slack oder E-Mail — alles, was du an einen Kanal routen kannst, kannst du auch an einen Webhook routen: Incidents, Warnungen, DNS-Änderungen, Zertifikatserneuerungen und sicherheitsrelevante Kontoereignisse.

Nicht die Reseller-Webhooks

Diese Seite beschreibt den Vertrag für Kundinnen und Kunden. Hoppla hat zusätzlich ein Reseller-Webhook-System mit eigenem Katalog — team.*, member.*, email.*-Unterdrückung — beschrieben unter Reseller-Webhooks. Diese Events gehen ausschließlich an Reseller-Tarife, und die Reseller-API ebenso. Alles auf DIESER Seite funktioniert in jedem kostenpflichtigen Tarif.

Einrichten ​

  1. Einstellungen → Benachrichtigungskanäle → Kanal hinzufügen, Typ Webhook.
  2. Trag die URL ein, die den POST empfangen soll.
  3. Optional ein HMAC-Secret setzen. Tu es — ohne Secret kannst du nicht nachweisen, dass eine Anfrage wirklich von Hoppla kam.
  4. Test senden schickt sofort eine echte Anfrage, damit du den Endpunkt prüfen kannst, bevor ein Incident davon abhängt.
  5. Richte eine Benachrichtigungsregel auf den Kanal, um auszuwählen, welche Events er bekommt.

Die URL muss öffentlich auflösbar sein. Private, Link-Local- und Loopback-Adressen werden beim Speichern und noch einmal bei der Zustellung abgelehnt — Hoppla ruft weder localhost noch 10.0.0.0/8 oder 169.254.0.0/16 auf. Für lokale Tests brauchst du also einen Tunnel.

Die Anfrage ​

POST, Content-Type: application/json, 10 Sekunden Timeout.

Nur ein 2xx gilt als zugestellt. Alles andere ist ein Fehlschlag — ein 4xx, ein 5xx oder eine Weiterleitung, der wir nicht folgen — ebenso ein Timeout oder ein Verbindungsfehler.

Was danach passiert, hängt vom Code ab. Ein 5xx, 429, 408 oder 425 sowie jeder Verbindungsfehler gelten als vorübergehendes Problem auf deiner Seite und werden wiederholt: drei Versuche nach 10 s, 60 s und 300 s. Jeder andere 4xx sagt, dass die Anfrage selbst falsch war — ein 404 für eine umgezogene URL, ein 422 für eine von deinem Handler abgelehnte Nachricht — und wird nicht wiederholt, weil die nächsten beiden Versuche identisch wären. So oder so wird die Zustellung protokolliert, samt dem Status deines Endpunkts, unter Benachrichtigungen → Protokoll. Jede Zeile dort hat einen Erneut senden-Knopf: er wiederholt die Zustellung mit derselben X-Webhook-Delivery-ID. Hat dein Endpunkt den ersten Versuch schon verarbeitet, erkennt er die Wiederholung und verwirft sie. Den Knopf zu drücken, wenn du unsicher bist, ob etwas angekommen ist, ist also unbedenklich.

Jede Anfrage trägt einen X-Webhook-Delivery-Header, im Body wiederholt als delivery_id. Er identifiziert eine Zustellung und ändert sich bei Wiederholungen nicht — genau darauf dedupliziert man. Siehe Mehrfache Zustellung.

Der User-Agent nennt die sendende Plattform, etwa Hoppla-Webhook/1.0. Auf einem White-Label-Portal steht dort stattdessen dessen Marke.

Der Body ist ein flaches JSON-Objekt — keine verschachtelten monitor- oder incident-Objekte — mit alphabetisch sortierten Schlüsseln. Jede Nachricht enthält event und timestamp, der Rest hängt vom Event ab.

json
{
  "check_type": "uptime",
  "delivery_id": "018f3c2a-7b41-7c3e-9a55-2f9b1d4e6a10",
  "event": "incident.created",
  "incident_id": "9f1c…",
  "monitor_id": "3ab7…",
  "monitor_name": "example.com",
  "monitor_url": "https://example.com",
  "severity": "critical",
  "started_at": "2026-03-04T12:00:00+00:00",
  "status": "down",
  "timestamp": "2026-03-04T12:00:03+00:00",
  "title": "HTTP 503 Service Unavailable"
}

Die Felder stammen aus einer festen Allowlist, damit eine Nachricht nie mehr aus deinem Konto mitnimmt, als das Event braucht: incident_id, monitor_id, monitor_name, monitor_url, title, status, severity, started_at, resolved_at, check_type, event_type, timestamp, zone, nameservers, changes, nameserver_change, nameservers_changed, ssl_issuer, ssl_valid_from, ssl_valid_to, response_time_ms, status_code, cleared_warning, brand_name. security.*-Events haben einen eigenen, kuratierten Payload und enthalten nie Zugangsdaten — nur den Namen dessen, was sich geändert hat.

Signatur prüfen ​

Hat der Kanal ein Secret, signiert Hoppla die Anfrage:

X-Webhook-Signature: <hex>

Bis zum 28. September 2026 trugen Zustellungen denselben Wert zusätzlich in X-Hoppla-Signature (und X-Hoppla-Timestamp). Dieses Paar ist entfernt. Liest dein Empfänger es noch, lies stattdessen X-Webhook-Signature (und X-Webhook-Timestamp); der Wert ist identisch.

Die Signatur ist HMAC-SHA256 über den rohen Request-Body, hex-kodiert:

signature = hmac_sha256(secret, roher_request_body)

Signiere die empfangenen Bytes, nicht ein neu serialisiertes Objekt. Hoppla sendet das JSON mit nicht-escapten Schrägstrichen und sortierten Schlüsseln; Parsen und neu Kodieren verändert die Bytes und die Signatur passt dann nicht mehr.

php
$raw = file_get_contents('php://input');
$expected = hash_hmac('sha256', $raw, $secret);

if (! hash_equals($expected, $_SERVER['HTTP_X_WEBHOOK_SIGNATURE'] ?? '')) {
    http_response_code(401);
    exit;
}
js
import { createHmac, timingSafeEqual } from 'node:crypto'

// express.raw({ type: 'application/json' }) — req.body muss ein Buffer sein
const expected = createHmac('sha256', secret).update(req.body).digest('hex')
const got = req.get('x-webhook-signature') ?? ''

const ok = expected.length === got.length &&
  timingSafeEqual(Buffer.from(expected), Buffer.from(got))
if (!ok) return res.sendStatus(401)
python
import hmac, hashlib

expected = hmac.new(secret.encode(), raw_body, hashlib.sha256).hexdigest()
if not hmac.compare_digest(expected, request.headers.get("X-Webhook-Signature", "")):
    abort(401)

Vergleiche in konstanter Zeit (hash_equals, timingSafeEqual, compare_digest), nicht mit ==.

Events ​

Jedes Event unten lässt sich an einen Webhook-Kanal routen. Dieselbe Liste steht im Produkt unter Developers und wird aus derselben Quelle erzeugt wie diese Seite.

certificate ​

EventBedeutung
certificate.renewedEin TLS-Zertifikat wurde durch ein neues ersetzt.

dns ​

EventBedeutung
dns.records_changedEin überwachter DNS-Eintrag hat seinen Wert geändert.

incident ​

EventBedeutung
incident.createdFür einen Monitor wurde ein Incident eröffnet.
incident.resolvedEin offener Incident wurde geschlossen und die Entwarnung verschickt.

monitor ​

EventBedeutung
monitor.flappingEin Monitor hat in kurzer Zeit wiederholt den Zustand gewechselt.
monitor.flapping_resolvedEin flappender Monitor hat sich beruhigt.

security ​

EventBedeutung
security.mfa_enabledEin Mitglied hat Zwei-Faktor-Authentifizierung aktiviert.
security.mfa_disabledEin Mitglied hat Zwei-Faktor-Authentifizierung deaktiviert.
security.recovery_codes_regeneratedEin Mitglied hat neue Recovery-Codes erzeugt; die alten sind ungültig.
security.mfa_resetEine Administratorin hat die Zwei-Faktor-Authentifizierung eines Mitglieds zurückgesetzt.
security.passkey_addedEin Mitglied hat einen Passkey registriert.
security.passkey_removedEin Passkey wurde von einem Konto entfernt.
security.password_changedEin Mitglied hat sein Passwort geändert.
security.email_changedDie E-Mail-Adresse eines Mitglieds wurde geändert.
security.api_token_createdEin API-Token wurde ausgestellt.
security.api_token_revokedEin API-Token wurde widerrufen.
security.session_revokedEine Sitzung wurde aus der Ferne abgemeldet.
security.login_new_deviceEine Anmeldung kam von einem Gerät, das dieses Konto noch nie benutzt hat.
security.account_blockedEin Konto wurde gesperrt und kann sich nicht mehr anmelden.
security.account_unblockedEin gesperrtes Konto wurde wiederhergestellt.
security.role_changedDie Rolle eines Mitglieds hat sich geändert und damit seine Rechte.
security.auth_policy_changedDie Authentifizierungs-Richtlinie des Teams hat sich geändert (SSO, MFA-Pflicht).
security.account_deletedEin Konto wurde gelöscht. Für die DSGVO-Löschung siehe member.erased.

storm ​

EventBedeutung
storm.detectedViele Monitore sind gleichzeitig ausgefallen — meist gemeinsame Infrastruktur, nicht viele Einzelfehler.
storm.resolvedEin erkannter Sturm ist vorüber.

warning ​

EventBedeutung
warning.ssl_expiringEin TLS-Zertifikat läuft demnächst ab.
warning.domain_expiringEine Domain-Registrierung läuft demnächst ab.
warning.degraded_performanceDie Antwortzeiten liegen dauerhaft über dem Schwellwert des Monitors.
warning.slow_responseEin einzelner Check hat den Schwellwert für langsame Antworten überschritten.
warning.application_healthEin Application-Health-Endpunkt meldet ein Problem.
warning.sitemap_issuesDie Sitemap ist nicht erreichbar, fehlerhaft oder enthält tote URLs.
warning.security_headersErwartete Security-Header fehlen oder wurden abgeschwächt.
warning.redirect_chainEine Redirect-Kette ist zu lang, läuft im Kreis oder fällt auf HTTP zurück.
warning.robots_txtrobots.txt hat sich geändert, fehlt oder blockiert jetzt das Crawling.
warning.dnsbl_listingDer Host steht auf einer DNS-Blockliste.
warning.broken_linksEin Crawl hat Links gefunden, die nicht mehr auflösen.
warning.mixed_contentEine HTTPS-Seite lädt Unterressourcen über unverschlüsseltes HTTP.
warning.server_resourcesEin Agent-Monitor meldet Druck auf CPU, Speicher oder Festplatte.
warning.clearedEine zuvor gemeldete Warnung besteht nicht mehr.

Mehrfache Zustellung ​

Die Zustellung ist at-least-once. Ein langsamer Endpunkt, der am Ende doch antwortet, hat den ersten Versuch womöglich schon verarbeitet — dasselbe Event kann also berechtigterweise zweimal ankommen.

Dedupliziere auf X-Webhook-Delivery (im Body delivery_id). Der Wert gehört zu genau einer Zustellung und ist bei jeder Wiederholung derselbe. Es genügt also, die bereits verarbeiteten zu merken und Wiederholungen zu überspringen:

php
$id = $request->header('X-Webhook-Delivery');

if (! Cache::add("webhook:{$id}", true, now()->addDay())) {
    return response()->noContent();   // schon verarbeitet
}

Eine Wiederholung benutzt dieselbe ID, ein wirklich neues Event bekommt eine neue. Nimm nicht incident_id plus event als Schlüssel: ein Monitor, der sich erholt und erneut ausfällt, erzeugt zwei echte incident.created-Events, die du beide willst.

Wissenswertes ​

  • Die Reihenfolge ist nicht garantiert. Zwei Events aus derselben Sekunde können in beliebiger Reihenfolge ankommen. Verlass dich auf timestamp, nicht auf den Eingang.
  • Antworte schnell. Das Timeout liegt bei 10 Sekunden; nimm die Arbeit in eine Queue und antworte sofort mit 2xx, statt inline zu verarbeiten.
  • Ein Secret ist optional, ohne eines ist nichts prüfbar. Dann trägt die Anfrage gar keine Signatur, und wer die URL kennt, kann darauf posten.

Einschränkungen ​

Jede Zustellung ist ein POST mit Content-Type: application/json. Die Methode lässt sich nicht ändern, eigene Header lassen sich nicht ergänzen — bewusst: die Anfrage authentifiziert sich über ihre HMAC-Signatur, nicht über etwas, das man in einen Header einträgt.

Steck auch kein Token in die URL. Die URL des Kanals wird allen im Team, die den Kanal lesen dürfen, vollständig angezeigt — sie ist kein Ort für ein Geheimnis. Nimm die Signatur von oben.