Zum Inhalt springen

Alarm-Webhook

Was Vaicoa an ein Alarmziel sendet. Wie du eines einrichtest, steht unter Alarme dorthin schicken, wo du arbeitest.

Ziele richtet ein Admin im Portal unter Alarme → Alarmziele ein. Vaicoa Pro stellt an ein Ziel zu, Vaicoa Team an bis zu fünf.

FeldZielHinweise
NameWebhook, PagerDutybis zu 60 Zeichen, eindeutig im Team. Erscheint auf der Seite, in Alarmen über das Ziel und im Audit-Trail; ist er leer, wird stattdessen die Adresse angezeigt
Alarme an diese URL sendenWebhooknur https://, bis zu 500 Zeichen, ohne Benutzername oder Passwort. Verschlüsselt gespeichert und nie wieder angezeigt: Das Portal zeigt nur Schema und Host, weil bei vielen Empfängern der Pfad die Zugangsdaten sind. Zum Ändern bearbeitest du das Ziel und fügst die vollständige URL neu ein
Welche AlarmeWebhook, PagerDutyWarnungen und kritische Alarme oder Nur kritische Alarme, je Ziel. Push-Mitteilungen sind nicht betroffen
Was wir sendenWebhookStandard-Payload oder Coding-Agent-Bericht (siehe unten)
Authentifizierungs-Header (optional), Wert des HeadersWebhookein Header, den dein Endpunkt erwartet, zum Beispiel Authorization. Der Wert wird verschlüsselt gespeichert und nie wieder angezeigt. Beim Bearbeiten heißt das Feld Wert des Headers ersetzen: Leer behält es den gespeicherten Wert. Löschst du den Namen, wird der Wert gelöscht. Testereignis senden schickt den Header mit
Service-RegionPagerDutyVereinigte Staaten oder Europäische Union
Routing-Key der IntegrationPagerDutyaus der Events-API-v2-Integration, die du in PagerDuty anlegst. Verschlüsselt gespeichert und nie wieder angezeigt. Vaicoa kann keinen neuen ausstellen, weil PagerDuty das tut. Beim Bearbeiten heißt das Feld Routing-Key ersetzen: Füg einen neuen Key ein oder lass es leer, um den gespeicherten zu behalten

Verschiedene Ziele können verschiedene Alarme bekommen: zum Beispiel PagerDuty für die Rufbereitschaft, alle Warnungen an einen Technik-Kanal und nur kritische Alarme an einen Agent-Endpunkt. Entfernen stoppt ein Ziel. Legst du es später wieder an, werden frühere Alarme nicht nachgeschickt. Ein Planwechsel löscht nie ein Ziel: Erlaubt der neue Plan weniger, stellen die ältesten weiter zu, und die übrigen bleiben pausiert gespeichert, bis der Plan sie wieder erlaubt oder du ein älteres entfernst.

Ein Webhook-Ziel bekommt einen POST-Request, jedes Mal, wenn ein Alarm beginnt oder beendet wird, und wenn du Testereignis senden nutzt.

HeaderInhalt
Content-Typeapplication/json
User-AgentCloudViewer-Webhook/1
X-CloudViewer-Eventder Wert von event, damit du ohne Parsen des Bodys weiterleiten kannst
X-CloudViewer-Deliveryeine ID, die bei Wiederholungen gleich bleibt; speicher sie, wenn du Idempotenz brauchst. Sie entspricht der id im Body
X-CloudViewer-Signaturesha256=<hex>, ein HMAC-SHA256 des rohen Bodys
dein eigener Headernur, wenn du am Ziel Authentifizierungs-Header (optional) gesetzt hast

Dein eigener Header darf keiner sein, den Vaicoa setzt: Content-Type, Content-Length, User-Agent, Host, Transfer-Encoding oder ein Name, der mit X-CloudViewer- beginnt.

Der Body ist UTF-8-JSON, kompakt gesendet (ohne Leerzeichen) und mit sortierten Schlüsseln. Das Beispiel unten ist zum Lesen formatiert.

Jedes Ereignis und jeder Empfänger bekommt dieselbe JSON-Struktur:

{
"version": 1,
"event": "alert.firing",
"id": "9f2c…",
"sent_at": "2026-08-16T09:12:33Z",
"team": { "id": 42, "name": "Ops", "slug": "ops" },
"alert": {
"fingerprint": "a1b2c3…",
"rule": "disk_space_critical",
"severity": "critical",
"status": "firing",
"summary": "Disk almost full",
"description": "Disk 96% full on web-1 (/var)",
"started_at": "2026-08-16T09:11:02Z",
"resolved_at": null
},
"resource": { "id": "srv_1", "project_id": 10, "server_id": 7 },
"links": {
"app": "cloudviewer://alert/a1b2c3…?resource=srv_1&project=10&team=42",
"portal": "https://account.vaicoa.com/a/ops/alerts/"
}
}
FeldBedeutung
versiondie Schema-Version, 1. Neue Felder ändern sie nicht; sie ändert sich nur bei einer Änderung, an der ein Empfänger scheitern könnte
eventalert.firing, alert.resolved oder test
iddie Zustell-ID, dieselbe wie in X-CloudViewer-Delivery
sent_atwann dieser Request gesendet wurde, in UTC. Das Feld steht im signierten Body
teamdie numerische ID, der Name und der Slug des Teams
alert.fingerprintdie Identität des Alarms. Zusammen mit started_at bezeichnet sie eine Alarm-Episode
alert.ruleder Schlüssel der Regel, zum Beispiel disk_space_critical. Siehe Alarmregeln
alert.severitywarning oder critical
alert.statusfiring oder resolved
alert.summary, alert.descriptionder Text der Regel für diesen Alarm
alert.started_atwann der Alarm begonnen hat, in UTC
alert.resolved_atnull, außer das Ereignis ist alert.resolved
resource.iddie Ressource, an der der Alarm hängt, oder ein leerer String
resource.project_id, resource.server_iddas verbundene Projekt und der überwachte Server, oder null
links.appderselbe Deep Link, den die Push-Mitteilung verwendet. Auf dem Handy öffnet er den Alarm in der App, wo du ihn stummschalten kannst
links.portaldie Seite Alarme des Teams im Portal

Ein Alarm, der beginnt und endet, bevor er zugestellt wurde, wird einmal gesendet, als alert.resolved, mit beiden Zeitstempeln.

Das Ereignis test hat dieselbe Form: alert.rule ist webhook_test, alert.severity ist warning, alert.summary ist ein fester Testtext, resource.id und links.app sind leer, und project_id und server_id sind null. Seine id beruht auf der Uhrzeit und kollidiert deshalb nie mit einem echten Alarm.

  • Ein Request je Ziel und Ereignis, sobald sich der Alarm ändert: ohne Bündelung und ohne Verzögerung. Jedes Ziel bekommt eine eigene Kopie, ein ausgefallenes Ziel betrifft die anderen also nicht.
  • Nur Warnungen und kritische Alarme. Informative Alarme bleiben in der App. Ein Ziel mit Nur kritische Alarme bekommt keine Warnungen. Stummgeschaltete Alarme werden nie gesendet.
  • Angenommen heißt: jede 2xx-Antwort. Der Body der Antwort wird bis zu 2 KiB gelesen und verworfen.
  • Timeout: 5 Sekunden für die Verbindung und die Antwort.
  • Weiterleitungen werden nicht verfolgt. Eine 3xx-Antwort zählt als Fehler.
  • Wiederholung: einmal pro Minute. Eine fehlgeschlagene Zustellung wird in der nächsten Minute wiederholt, mit derselben Zustell-ID, bis sie gelingt oder das Ziel abgeschaltet ist. Eine Änderung, die älter als sechs Stunden ist, wird nicht mehr gesendet. Nach einem Fehler warten die übrigen Ereignisse für dieses Ziel auf die nächste Minute.
  • Abgeschaltet nach 20 Fehlversuchen in Folge, also nach etwa zwanzig Minuten. Eine erfolgreiche Zustellung setzt den Zähler zurück. Wieder einschalten auf der Seite Alarme oder das Speichern des Ziels schaltet es wieder ein.
  • Höchstens 20 Ereignisse je Team und Minute. Der Rest folgt in der nächsten Minute.
  • Nur öffentliche https-Adressen. Eine URL muss mit https:// beginnen und darf keinen Benutzernamen und kein Passwort enthalten. Eine URL, die auf eine private oder interne Adresse zeigt, wird beim Speichern abgelehnt und noch einmal vor jedem Request.
  • Ein neues Ziel bekommt keine älteren Alarme. Die Zustellung beginnt mit den Alarmen, die sich ändern, nachdem du es angelegt hast.

Das Geheimnis wird einmal angezeigt: beim Anlegen des Ziels und jedes Mal, wenn du Neues Signatur-Geheimnis ausstellen nutzt. Beim Bearbeiten eines Ziels bleibt sein Geheimnis erhalten. Berechne den HMAC über den rohen Request-Body, vor dem Parsen des JSON, und vergleiche in konstanter Zeit:

import hashlib, hmac
def verify(raw_body: bytes, header: str, secret: str) -> bool:
expected = "sha256=" + hmac.new(secret.encode(), raw_body, hashlib.sha256).hexdigest()
return hmac.compare_digest(expected, header)

sent_at ist Teil des signierten Bodys. Du kannst also auch Requests ablehnen, die älter sind als zum Beispiel fünf Minuten.

Ein Webhook-Ziel kann statt des Standard-Payloads den Coding-Agent-Bericht senden: Wähl unter Was wir senden die Option Coding-Agent-Bericht. Der Request ist dasselbe Objekt mit derselben Signatur und derselben version, plus ein agent-Objekt:

"agent": {
"preamble": "You are being handed a production alert from …",
"brief_format": "markdown",
"brief": "# Incident brief — disk_space_critical on srv_1\n\n…"
}
FeldBedeutung
preambledie Hinweise für Alarm-Übergaben deines Teams aus Agent-Übergabehinweise im Portal, oder der Standardtext von Vaicoa, wenn das Team keine hat
brief_formatmarkdown
briefein kurzer Vorfallsbericht in Markdown: ein Titel, ein Abschnitt Alert (Regel, Schwere, Status, Zeiten, Zusammenfassung, Beschreibung auf 500 Zeichen gekürzt), ein Abschnitt Resource und ein Abschnitt Links. Ein Abschnitt ohne Inhalt wird weggelassen, nicht mit „none“ gefüllt

Der Bericht entsteht nur aus dem, was das Standard-Objekt enthält (Alarm, Ressource, Links), und gibt deshalb nichts zusätzlich preis. Er ist kürzer als der Bericht aus der App: Letzte Änderungen, Metrik-Verläufe und Kosteninformationen gibt es nur in der App, und der Webhook-Bericht lässt sie weg. Auch das Ereignis test trägt das agent-Objekt, damit du siehst, was dein Agent bekommen wird. Für eine Chat-Brücke nimm den Standard-Payload. Mehr zu Berichten: An einen Coding-Agent übergeben.

Der Payload ist generisches JSON. Er hat nicht das Nachrichtenformat von Slack, Discord oder Teams. Richtest du eine Incoming-Webhook-URL eines dieser Dienste direkt bei Vaicoa ein, wird die Nachricht nicht richtig dargestellt, weil jeder Dienst sein eigenes Format erwartet.

Setz eine kleine Brücke dazwischen: einen Cloudflare Worker, eine Lambda-Funktion, einen Schritt in n8n oder Make oder ein paar Zeilen Code in etwas, das bei dir ohnehin läuft. Die Brücke nimmt das JSON-Objekt an, prüft die Signatur und sendet das Format, das dein Chat-Dienst erwartet.

Sendest du an deinen eigenen Endpunkt (ein Incident-Werkzeug, ein Skript, eine Queue), brauchst du keine Brücke. Das Objekt oben ist das vollständige Format.

Ein PagerDuty-Ziel (Vaicoa Team) sendet Ereignisse der PagerDuty Events API v2 an die Region, die du ausgewählt hast:

RegionEndpunkt
Vereinigte Staatenhttps://events.pagerduty.com/v2/enqueue
Europäische Unionhttps://events.eu.pagerduty.com/v2/enqueue

Der Routing-Key steht im Body, deshalb gibt es keine Signatur und keinen X-CloudViewer-*-Header. Timeout, Wiederholung und Abschalten folgen denselben Regeln wie beim Webhook. Jede 2xx-Antwort gilt als angenommen.

VaicoaPagerDuty-Ereignis
alert.firingevent_action: "trigger"
alert.resolvedevent_action: "resolve", nur mit routing_key, event_action und dedup_key
eine Alarm-Episodeein dedup_key: cv- und 32 Hex-Zeichen, ein Hash aus Team, Ziel, Fingerprint des Alarms und seiner Startzeit. Trigger und Resolve tragen denselben Key, deshalb schließt der Resolve den Incident, den der Trigger eröffnet hat
alert.summarypayload.summary, auf 1.024 Zeichen gekürzt (der Schlüssel der Regel, wenn es keine Zusammenfassung gibt)
alert.severitypayload.severity: critical oder warning
resource.idpayload.source; server <id>, wenn es keine Ressource gibt, der Slug des Teams, wenn es beides nicht gibt
alert.started_atpayload.timestamp, der Beginn des Alarms selbst, nicht der Sendezeitpunkt
links.applinks: [{"href": …, "text": …}], ein Link, der den Alarm in der App öffnet
links.portalclient_url, mit dem Namen des Dienstes in client
Regel, Fingerprint, IDs, Beschreibungpayload.custom_details: rule, fingerprint, project_id, server_id, resource_id, description

Ein Alarm, der elfmal flattert, erzeugt elf Incidents. Zum Zusammenfassen nutzt du die Suppression-Regeln von PagerDuty. Zwei PagerDuty-Ziele eröffnen getrennte Incidents.

Testereignis senden schickt einen trigger mit einer festen Zusammenfassung, die sagt, dass kein Alarm aktiv ist, der Schwere warning und einem Dedup-Key, der mit cv-test- beginnt, und danach einen resolve dafür. Es kann keinen echten Incident schließen.