Skip to content

Webhooks ​

Empfange Echtzeit-Benachrichtigungen, wenn Teams erstellt oder gelöscht werden, Monitore sich ändern, Vorfälle in verwalteten Teams auftreten oder Plattform-E-Mails unterdrückt werden.

Einrichtung ​

Option A: Komfort-API (empfohlen) ​

Erstell ein Webhook-Abonnement in einem einzigen Aufruf:

bash
curl -X POST https://app.hoppla.dev/api/v1/reseller/webhooks \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Ops Integration",
    "url": "https://your-server.com/hoppla-webhook",
    "secret": "your-hmac-secret",
    "events": [
      "monitor.down",
      "monitor.up",
      "incident.created",
      "incident.resolved",
      "ssl.expiring",
      "team.provisioned"
    ],
    "tenant_filter": null
  }'

Damit werden Benachrichtigungskanal und -regel automatisch erstellt. Verwalten kannst du sie über GET/PATCH/DELETE /reseller/webhooks/{id}. Zustellprotokolle rufst du mit GET /reseller/webhooks/{id}/deliveries ab.

Option B: Manuelle Einrichtung (fortgeschritten) ​

Für die volle Kontrolle über die Kanaleinstellungen (Ruhezeiten, Rate-Limits, Batch-Fenster) nutzt du die Standard-Endpoints für Kanal + Regel:

1. Benachrichtigungskanal erstellen ​

bash
curl -X POST https://app.hoppla.dev/api/v1/notification-channels \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Provisioning Webhooks",
    "type": "webhook",
    "config": {
      "url": "https://your-server.com/hoppla-webhook",
      "secret": "your-hmac-secret"
    },
    "is_enabled": true
  }'

2. Benachrichtigungsregel erstellen ​

bash
curl -X POST https://app.hoppla.dev/api/v1/notification-rules \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "channel_id": "channel-uuid-...",
    "event_types": [
      "team.provisioned",
      "team.deleted",
      "incident.created",
      "incident.resolved",
      "monitor.down",
      "ssl.expiring"
    ],
    "tenant_filter": {
      "tenant_ids": ["uuid1", "uuid2"],
      "labels": ["production-*"]
    }
  }'

Mandanten-Filterung ​

Lege fest, welche verwalteten Mandanten Events für jeden Webhook auslösen:

FilterBeschreibung
null / weggelassenAlle verwalteten Mandanten (Standard)
tenant_idsExplizite Liste von Tenant-UUIDs
labelsMustervergleich auf Labels verwalteter Teams (unterstützt nachgestelltes *-Wildcard)

Filter werden mit ODER verknüpft: Ein Event passt, wenn es zu irgendeiner tenant_id ODER zu irgendeinem Label-Muster passt.

Vollständige Event-Referenz ​

Team-Verwaltungs-Events ​

EventAuslöserWichtige Payload-Felder
team.provisionedTeam über API oder Portal-Registrierung erstelltmanaged_team_id, tenant_id, tenant_name, label
team.deletedTeam soft-gelöschtmanaged_team_id, tenant_id, tenant_name
team.pausedMonitoring für ein Team pausiertmanaged_team_id, tenant_id, reason
team.unpausedMonitoring wieder aufgenommenmanaged_team_id, tenant_id
team.trial_endingTestphase endet in 3 Tagen oder 1 Tagmanaged_team_id, tenant_id, plan, trial_ends_at, days_remaining
team.trial_expiredTestphase abgelaufen, Plan auf Free zurückgesetztmanaged_team_id, tenant_id, previous_plan
tenant.quota_warningEin verwaltetes Team erreicht ≥80 % eines Plan-Limits (z. B. Monitore)tenant_id, feature, current_count, limit, usage_pct
tenant.quota_exceededEine Erstellung wurde blockiert, weil ein verwaltetes Team ein Plan-Limit erreicht hattenant_id, feature, current_count, limit, usage_pct

Quota-Events werden pro Team + Feature entprellt (ein tenant.quota_warning pro Tag, ein tenant.quota_exceeded pro 6 Stunden), damit ein Batch-Import deinen Webhook nicht überflutet.

Monitor-Events ​

EventAuslöser
monitor.createdMonitor in einem verwalteten Team erstellt
monitor.updatedMonitor in einem verwalteten Team aktualisiert
monitor.deletedMonitor in einem verwalteten Team gelöscht
monitor.downMonitor ausgefallen (Convenience-Alias für incident.created)
monitor.upMonitor wiederhergestellt (Convenience-Alias für incident.resolved)
monitor.enabledMonitor aktiviert (war pausiert/deaktiviert)
monitor.disabledMonitor deaktiviert/pausiert
monitor.flappingMonitor flattert (wechselt schnell zwischen erreichbar/ausgefallen)
monitor.flapping_resolvedMonitor flattert nicht mehr und hat sich stabilisiert
ssl.expiringSSL-Zertifikat läuft innerhalb des Schwellwerts ab (Alias für warning.ssl_expiring)

Mitglieder-Events ​

EventAuslöser
member.addedBenutzer zu einem verwalteten Team hinzugefügt
member.removedBenutzer aus einem verwalteten Team entfernt
member.erasedPersonenbezogene Daten eines Mitglieds nach DSGVO Art. 17 gelöscht

member.erased ist nicht member.removed. Entfernen gibt einen Platz frei: die Person existiert weiter und kann erneut eingeladen werden. Löschen vernichtet ihre Identität plattformweit — Login, Passkeys, Geräte-Tokens und jedes Team, dem sie angehörte, auch Teams, die du nicht verwaltest. Wenn dein CRM member.removed als „Platz frei, Kontakt behalten" behandelt, ist das hier falsch: der Kontaktdatensatz ist genau das, was weg muss.

Das Event wird ausgelöst, egal ob die Person sich selbst in ihren Kontoeinstellungen gelöscht hat oder du es in ihrem Auftrag über POST /reseller/managed-teams/{managedTeam}/members/{userId}/erase getan hast. initiated_by im Payload ist self oder reseller.

Das Event enthält die E-Mail-Adresse bewusst — sie ist der einzige Schlüssel, über den du deine eigenen Datensätze zuordnen kannst, und dies ist die letzte Nachricht, die sie transportieren kann.

Monitoring-Events (Weitergeleitet) ​

Alle Monitoring-Events deiner verwalteten Teams werden automatisch an deine Benachrichtigungskanäle weitergeleitet. Du hast immer volle Sichtbarkeit.

KategorieEvent-Typen
Vorfälleincident.created, incident.resolved
Warnungenwarning.ssl_expiring, warning.domain_expiring, warning.degraded_performance, warning.application_health, warning.sitemap_issues, warning.security_headers, warning.redirect_chain, warning.robots_txt, warning.dnsbl_listing, warning.broken_links, warning.mixed_content, warning.server_resources, warning.cleared
DNSdns.records_changed, dns.nameserver_changed
Zertifikatecertificate.renewed
Stormsstorm.detected, storm.resolved

dns.nameserver_changed ist ein Convenience-Alias, der zusätzlich zu dns.records_changed ausgelöst wird, sobald eine bestätigte DNS-Änderung auch den Satz der autoritativen Nameserver betrifft (ein Indikator für ein NS-Hijacking). Er trägt denselben Payload wie dns.records_changed. Wie alle weitergeleiteten DNS-Events wird er unabhängig vom notify_dns_changes-Schalter des verwalteten Kunden zugestellt — schaltet ein Kunde seine eigenen DNS-Alarme stumm, bleibst du als Reseller dennoch über eine Nameserver-Änderung in deinem Bestand informiert.

E-Mail-Unterdrückungs-Events ​

Wenn du Plattform-E-Mails unterdrückst, sendet Hoppla email.*-Webhooks mit dem vollständigen E-Mail-Payload — damit du die E-Mails selbst versenden kannst.

EventUnterdrückte E-Mail
email.password_resetWird nie ausgelöst — Passwort-Reset-Mails können nicht unterdrückt werden.
email.email_change_verifyWird nie ausgelöst — die Verifizierung einer Adressänderung kann nicht unterdrückt werden.
email.email_changed_notifyWird nie ausgelöst — die Nachricht an die alte Adresse kann nicht unterdrückt werden.
email.invitationUnterdrückte Team-Einladung.
email.member_removedUnterdrückte Nachricht über das Entfernen aus einem Team.
email.trial_endingUnterdrückte Erinnerung an das Ende der Testphase.
email.trial_expiredUnterdrückte Nachricht über die abgelaufene Testphase.
email.incident_createdUnterdrückter Alarm zu einem eröffneten Vorfall.
email.incident_resolvedUnterdrückte Entwarnung zu einem behobenen Vorfall.
email.warningUnterdrückte Hinweis-Mail zu einer Warnung.
email.dns_changedUnterdrückte DNS-Änderungs-Mail. Kein Live-Feed für DNS-Änderungen — dafür dns.records_changed / dns.nameserver_changed abonnieren.
email.certificate_renewedUnterdrückte Nachricht über ein erneuertes Zertifikat.
email.digestUnterdrückte Sammel-Benachrichtigung.
email.monthly_reportUnterdrückter monatlicher Monitoring-Bericht.
email.status_page_subscriptionUnterdrücktes Status-Seiten-Update an einen Abonnenten.
email.security_mfa_enabledUnterdrückte Nachricht, dass die Zwei-Faktor-Authentifizierung aktiviert wurde.
email.security_mfa_disabledUnterdrückte Nachricht, dass die Zwei-Faktor-Authentifizierung deaktiviert wurde.
email.security_recovery_codes_regeneratedUnterdrückte Nachricht, dass die Wiederherstellungscodes ersetzt wurden und die alten ungültig sind.
email.security_mfa_resetUnterdrückte Nachricht, dass die Zwei-Faktor-Authentifizierung administrativ zurückgesetzt wurde.
email.security_passkey_addedUnterdrückte Nachricht, dass ein Passkey zum Konto hinzugefügt wurde.
email.security_passkey_removedUnterdrückte Nachricht, dass ein Passkey vom Konto entfernt wurde.
email.security_password_changedUnterdrückte Nachricht, dass das Kontopasswort geändert wurde.
email.security_email_changedUnterdrückte Nachricht, dass die E-Mail-Adresse des Kontos geändert wurde.
email.security_api_token_createdUnterdrückte Nachricht, dass ein API-Token ausgestellt wurde.
email.security_api_token_revokedUnterdrückte Nachricht, dass ein API-Token widerrufen wurde.
email.security_session_revokedUnterdrückte Nachricht, dass eine angemeldete Sitzung aus der Ferne beendet wurde.
email.security_login_new_deviceUnterdrückte Nachricht über eine Anmeldung von einem bisher unbekannten Gerät.
email.security_account_blockedUnterdrückte Nachricht, dass das Konto für Anmeldungen gesperrt wurde.
email.security_account_unblockedUnterdrückte Nachricht, dass eine Sperre des Kontos aufgehoben wurde.
email.security_role_changedUnterdrückte Nachricht, dass sich die Rolle des Kontos in einem Team geändert hat.
email.security_auth_policy_changedUnterdrückte Nachricht, dass sich die Anmelderichtlinie des Teams geändert hat.
email.security_account_deletedUnterdrückte Nachricht, dass das Konto gelöscht wurde.

Unterdrückung braucht einen Empfänger

Eine E-Mail wird nur unterdrückt, wenn du einen aktiven Webhook-Kanal mit einer Regel auf genau dieses email.*-Event hast. Fehlt sie, geht die Plattform-E-Mail ganz normal an den Benutzer, statt ersatzlos zu verschwinden. Erst abonnieren, dann unterdrücken.

Die Auth-Klasse wird nie unterdrückt

email.password_reset, email.email_change_verify und email.email_changed_notify stehen nur der Vollständigkeit halber hier und werden nie ausgelöst. Ihre Template-Daten sind die Zugangsdaten selbst — ein funktionierender Reset- oder Verifizierungslink. Ihn an einen Reseller zuzustellen wäre eine Kontoübernahme, von der der Benutzer nie erfährt. Nutze Impersonation, wenn du auf einem verwalteten Konto handeln musst; dieser Weg wird als Impersonation protokolliert.

Payload-Beispiele ​

Team erstellt ​

json
{
  "event": "team.provisioned",
  "timestamp": "2026-01-15T10:30:00.000000Z",
  "managed_team_id": "mt-uuid-...",
  "tenant_id": "new-tenant-uuid-...",
  "tenant_name": "Acme Corp",
  "label": "Enterprise-Kunde #42"
}

Testphase endet ​

json
{
  "event": "team.trial_ending",
  "timestamp": "2026-01-28T00:00:00.000000Z",
  "managed_team_id": "mt-uuid-...",
  "tenant_id": "tenant-uuid-...",
  "tenant_name": "Acme Corp",
  "label": "Enterprise-Kunde #42",
  "plan": "pro",
  "trial_ends_at": "2026-01-31T00:00:00.000000Z",
  "days_remaining": 3
}

Weitergeleiteter Vorfall ​

Weitergeleitete Monitoring-Events enthalten ein _managed_tenant-Objekt zur Identifikation des Teams:

json
{
  "event": "incident.created",
  "timestamp": "2026-01-15T14:00:00.000000Z",
  "incident_id": "inc-uuid-...",
  "monitor_id": "monitor-uuid-...",
  "monitor_name": "Acme Webseite",
  "monitor_url": "https://acmecorp.com",
  "title": "HTTP 503 Service Unavailable",
  "status": "investigating",
  "severity": "major",
  "started_at": "2026-01-15T14:00:00.000000Z",
  "resolved_at": null,
  "_managed_tenant": {
    "managed_tenant_id": "tenant-uuid-...",
    "name": "Acme Corp",
    "label": "Enterprise-Kunde #42"
  }
}

Weitergeleitete DNS-Änderung ​

Sowohl dns.records_changed als auch dns.nameserver_changed tragen denselben zonen-bewussten Payload. nameserver_change ist null (und nameservers_changed ist false) bei einer reinen Record-Änderung; ändert sich der Satz der autoritativen Nameserver, beschreibt es die Delegation vorher/nachher:

json
{
  "event": "dns.nameserver_changed",
  "timestamp": "2026-01-15T16:00:00.000000Z",
  "monitor_id": "monitor-uuid-...",
  "monitor_name": "Acme Webseite",
  "monitor_url": "https://acmecorp.com",
  "check_type": "dns",
  "zone": "acmecorp.com",
  "nameservers": ["ns1.evil-host.com", "ns2.evil-host.com"],
  "changes": [
    { "type": "NS", "old": ["ns1.acme-dns.com", "ns2.acme-dns.com"], "new": ["ns1.evil-host.com", "ns2.evil-host.com"] }
  ],
  "nameserver_change": {
    "old": ["ns1.acme-dns.com", "ns2.acme-dns.com"],
    "new": ["ns1.evil-host.com", "ns2.evil-host.com"]
  },
  "nameservers_changed": true,
  "_managed_tenant": {
    "managed_tenant_id": "tenant-uuid-...",
    "name": "Acme Corp",
    "label": "Enterprise-Kunde #42"
  }
}

E-Mail-Unterdrückungs-Webhook ​

Wenn eine Plattform-E-Mail unterdrückt wird, enthält der Webhook alles, was du brauchst:

json
{
  "event": "email.security_password_changed",
  "timestamp": "2026-01-15T15:00:00.000000Z",
  "email_type": "security_password_changed",
  "recipient": {
    "email": "[email protected]",
    "name": "John Doe",
    "language": "de",
    "user_id": "user-uuid-..."
  },
  "template_data": {
    "user_name": "John Doe",
    "user_email": "[email protected]",
    "brand_name": "Acme Monitoring",
    "occurred_at": "2026-01-15T14:59:58+00:00",
    "ip_address": "203.0.113.10"
  },
  "_managed_tenant": {
    "managed_tenant_id": "tenant-uuid-...",
    "name": "Acme Corp",
    "label": "Enterprise-Kunde #42"
  }
}

Dieses Beispiel zeigte früher email.password_reset mit einem funktionierenden reset_url. Dieses Event wird nicht ausgelöst und dieser Payload nie gesendet — siehe den Hinweis zur Auth-Klasse oben.

Wichtige Verhaltensweisen ​

  • Gleiche Event-Typ-Namen — Reseller abonnieren incident.created, nicht reseller.incident.created. Das _managed_tenant-Feld unterscheidet weitergeleitete Events.
  • Monitor-Stummschaltung wird umgangen — Wenn ein verwaltetes Team Benachrichtigungen für einen Monitor stummschaltet, erhältst du das weitergeleitete Event trotzdem.
  • Gecachte Abfragen — Zuordnungen verwalteter Teams werden 5 Minuten gecacht und automatisch invalidiert bei Änderungen.

E-Mail-Unterdrückung ​

Reseller können bestimmte Plattform-E-Mails für alle verwalteten Teams unterdrücken. Statt die E-Mail zu senden, feuert Hoppla einen email.*-Webhook mit Empfänger und Template-Daten, damit du sie mit deinen eigenen Templates und deinem Branding selbst verschickst.

Unterdrückung ist eine Umleitung, kein Ausschalter. Sie greift nur, wenn du das passende Event abonniert hast, und die Auth-Klasse wird überhaupt nie unterdrückt — siehe die beiden Hinweise oben.

Unterdrückung konfigurieren ​

bash
# Aktuelle Konfiguration abrufen
curl https://app.hoppla.dev/api/v1/reseller/email-suppression \
  -H "Authorization: Bearer $TOKEN"

# Unterdrückte E-Mail-Typen aktualisieren
curl -X PUT https://app.hoppla.dev/api/v1/reseller/email-suppression \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "suppressed_types": [
      "invitation",
      "member_removed",
      "incident_created",
      "incident_resolved",
      "warning"
    ]
  }'

So funktioniert es ​

  1. Konfiguriere über die API oder das Dashboard, welche E-Mail-Typen unterdrückt werden sollen
  2. Wenn Hoppla normalerweise eine dieser E-Mails an einen Benutzer eines verwalteten Teams senden würde, wird die Unterdrückungskonfiguration geprüft
  3. Wenn der E-Mail-Typ unterdrückt ist, feuert Hoppla stattdessen einen email.*-Webhook
  4. Dein System empfängt den Webhook mit dem vollständigen E-Mail-Payload und kann die E-Mail selbst versenden
  5. Nicht-E-Mail-Benachrichtigungskanäle (Slack, Telegram usw.) sind nicht betroffen — sie funktionieren weiterhin normal

Wichtige Hinweise ​

  • Die Unterdrückung gilt für alle verwalteten Teams (kontoweite Einstellung)
  • Nur der E-Mail-Kanal wird unterdrückt — andere Benachrichtigungskanäle funktionieren normal
  • Der Payload enthält die Template-Daten der ersetzten Mail. Passwort-Reset- oder Verifizierungslinks sind nie dabei, weil diese Mails nicht unterdrückt werden können
  • Ein Abonnement ist Pflicht. Einen Typ zu unterdrücken, den du nicht abonniert hast, hat keine Wirkung: die Plattform sendet die E-Mail normal. Das ist Absicht — die Alternative wäre eine Nachricht, die niemanden erreicht
  • Ist dein Endpunkt nicht erreichbar, bleibt die E-Mail trotzdem unterdrückt (kein Fallback auf Plattform-E-Mail). Behandle Zustellfehler auf email.* also als für den Benutzer sichtbar

Zustellung ​

  • Events werden asynchron im Hintergrund zugestellt
  • Fehlgeschlagene Zustellungen werden 3-mal mit Backoff wiederholt (10s, 60s, 300s)
  • HTTP-Zustellungs-Timeout: 10 Sekunden -- dein Endpoint muss innerhalb von 10s antworten, sonst gilt die Zustellung als fehlgeschlagen und wird wiederholt
  • Der Zustellungsstatus wird protokolliert und ist über die Benachrichtigungsprotokoll-API einsehbar

Unterstützte Kanäle ​

Reseller-Events können über jeden Benachrichtigungskanal-Typ zugestellt werden:

KanalBeschreibung
WebhookHTTP POST mit JSON-Payload und optionaler HMAC-Signatur
E-MailEvent-Details an konfigurierte Empfänger
SlackNachricht via Incoming Webhook
DiscordNachricht via Discord-Webhook
Microsoft TeamsNachricht via Teams Incoming Webhook
PagerDutyEvent-Weiterleitung über PagerDuty Events API
TelegramNachricht über Telegram Bot API

Webhook-Signaturen prüfen ​

Wenn du ein secret bei deinem Webhook-Kanal konfiguriert hast, enthält jede Anfrage einen X-Webhook-Signature-Header mit einer HMAC-SHA256-Signatur. (Der frühere Header X-Hoppla-Signature ist seit dem 28. September 2026 entfernt; er trug denselben Wert.)

Prüfe gegen die rohen Bytes des Request-Bodys -- nicht gegen ein neu serialisiertes Objekt. Die Signatur wird über genau die Bytes berechnet, die Hoppla sendet. Hoppla erzeugt diesen Body, indem die obersten Schlüssel alphabetisch sortiert und das JSON ohne maskierte Schrägstriche serialisiert wird. Wenn du das JSON parst und vor der Prüfung neu kodierst, unterscheiden sich die Bytes (Schlüsselreihenfolge, Schrägstrich-Maskierung, Leerzeichen) und die Prüfung schlägt fehl. Lies immer den rohen Request-Body und gib genau diese Bytes an deine HMAC-Funktion weiter.

python
import hmac
import hashlib

# payload_body MUSS der rohe Request-Body (Bytes) sein, vor jeglichem JSON-Parsing
def verify_signature(payload_body: bytes, secret: str, signature_header: str) -> bool:
    expected = hmac.new(
        secret.encode(),
        payload_body,
        hashlib.sha256
    ).hexdigest()
    return hmac.compare_digest(expected, signature_header)
php
// Roh-Body lesen, z. B. file_get_contents('php://input') -- kein dekodiertes Array neu kodieren
$expected = hash_hmac('sha256', $rawRequestBody, $secret);
$valid = hash_equals($expected, $signatureHeader);