PatchFirst / Dokumentation
Webhook-Benachrichtigungen
Die bezahlte Überwachung unterstützt ein HTTPS-Ziel je Serverbestand. PatchFirst sendet ausgewählte Ereignisse per POST mit Content-Type: application/json. Vollständige Befunde und Empfehlungen bleiben in Ihrer Übersicht.
Benachrichtigungen aktivieren
- Öffnen Sie Benachrichtigungen in der Übersicht, wählen Sie einen Serverbestand mit aktiver bezahlter Überwachung und anschließend Webhook.
- Geben Sie eine öffentliche HTTPS-URL ein. Standardport ist 443; andere Ports müssen ausdrücklich angegeben werden. Die URL darf keine Geheimnisse oder personenbezogenen Daten enthalten.
- Wählen Sie Ereignisse und die Stundenanzahl ohne Inventar bis zur Benachrichtigung über einen nicht mehr meldenden Server.
- Speichern Sie das vom Dienst erzeugte Geheimnis im sicheren Speicher Ihres Empfängers. Es wird nur einmal angezeigt. Wählen Sie „Test senden“, um Ihre Integration zu prüfen.
Aktualisieren Sie beim Geheimniswechsel Ihren Empfänger: Das alte Geheimnis wird ungültig. Einstellungsänderungen stornieren ausstehende Zustellungen; ein bereits laufender Versuch kann noch abgeschlossen werden. Das Speichern unveränderter Einstellungen erhält ausstehende Wiederholungen und den Benachrichtigungsstatus nicht meldender Server. Konfiguration und Versand erfordern aktiven bezahlten Zugang.
Ereignisse
| Ereignis | Auslöser |
|---|---|
finding.new | Neuer P0- oder P1-Befund: erstes Auftreten einer Komponente auf einem Server oder eine neue CVE für diese Komponente. |
finding.escalated | Ein vorhandener Befund wird auf P0 oder P1 hochgestuft. |
host.kev | Ein Befund erhält eine CVE, die selbst im KEV-Katalog aufgeführt ist. |
host.rating_dropped | Die Buchstabenbewertung eines Servers verschlechtert sich gegenüber der letzten bekannten Note, einschließlich Übergängen über „?“. |
host.inventory_stale | Seit der konfigurierten Stundenanzahl ist kein Inventar eingegangen. |
webhook.test | Der Eigentümer wählt „Test senden“. |
Ereignisse entstehen durch Inventarübermittlung oder Neuberechnung mit aktualisierten Feeds. Eine Neuberechnung bei unverändertem Zustand erzeugt kein weiteres Ereignis. Wiederholte Zustellversuche behalten dieselbe Ereignis-ID.
Ein Befund wird durch Server + Komponente/Paket identifiziert. Ein finding.new führt die wichtigsten CVEs des Befunds auf; Versand erfolgt beim ersten Auftreten oder bei einer neuen CVE für die Komponente. Ein Versionswechsel ohne neue CVEs oder eine andere Reihenfolge der wichtigsten CVEs erzeugt kein Ereignis. Der gesamte CVE-Satz wird verfolgt, auch CVEs außerhalb der ersten drei. Die Hochstufung eines bestehenden Befunds wird als finding.escalated gesendet. Absenkung und Rückkehr auf dieselbe Stufe innerhalb von 24 Stunden erzeugen keine erneute Benachrichtigung für diese Stufe. Verschwindet ein Befund und kehrt nach mindestens 24 Stunden zurück, löst er finding.new aus. Webhook-Aktivierung oder Wiederaufnahme der Zahlung initialisiert den Verlauf anhand des aktuellen Zustands ohne Benachrichtigungen; ein ausdrücklicher Testversand ist separat verfügbar. Der Verlauf wird auch bei deaktivierten oder unbezahlten Webhooks weitergeführt. Neue Befunde und Hochstufungen haben separate Ereigniseinstellungen in der Übersicht.
host.kev wird nur für CVEs mit eigenem KEV-Kennzeichen gesendet, einmal je Paar „Server, CVE“, auch wenn die CVE in mehreren Befunden erscheint. Ein KEV-Kennzeichen des Gesamtbefunds gilt nicht für dessen andere CVEs. Bei einem Bewertungsübergang B → ? → F enthält das Ereignis before: B und die after: F: „?“ bewahrt die letzte bekannte Buchstabennote.
JSON-Nutzdaten
{
"event": "finding.new",
"id": "aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa",
"created": 1792756800,
"fleet": "bbbbbbbb-bbbb-4bbb-8bbb-bbbbbbbbbbbb",
"host": {
"id": "cccccccc-cccc-4ccc-8ccc-cccccccccccc",
"name": "acme-1"
},
"summary": {
"cves": ["CVE-2026-12345", "CVE-2026-12346"],
"level": "P0",
"component": "acme",
"version": "1.2.3"
},
"url": "/app/#servers"
}
idist die Ereignis-UUID und entsprichtX-PatchFirst-Delivery;createdist der Erstellungszeitpunkt als ganzzahlige Unix-UTC-Sekunden;fleetist die Bestands-UUID.hostenthält die Server-UUID oder istnullbei einem Test.nameist nur bei einem offen gespeicherten Alias enthalten. Verschlüsselte Namen werden nie offengelegt oder gesendet. Ein offener Alias wird zum Zustellzeitpunkt gelesen und kann sich zwischen Versuchen ändern; die Warteschlange speichert nur die UUID.summaryist ein minimales ereignisspezifisches Objekt. Fürfinding.newund diefinding.escalated:cves(Liste mit bis zu drei wichtigsten CVEs),level,componentund dieversion(installierte Version). Fürhost.kev:cve(eine CVE aus KEV),level,componentund dieversion. Ist die installierte Version unbekannt, istversioneine leere Zeichenfolge. Für Bewertungsänderungen:beforeund dieafter; für fehlendes Inventar:hoursund dielast_received(Unix-UTC-Sekunden); für Tests:messagemit dem WertPatchFirst webhook test. Empfänger sollten neue Felder tolerieren.urlist ein relativer Übersichtslink auf dieser Website; Anmeldung erforderlich. Vollständiges Inventar, Namensverzeichnis, Tokens und Kundendaten sind ausgeschlossen.
Anfragesignatur
Prüfen Sie vor der Verarbeitung alle vier Header:
X-PatchFirst-Timestamp: ganzzahlige Unix-UTC-Sekunden des aktuellen Versuchs.X-PatchFirst-Signature:sha256=<hex>, der HMAC-SHA256 vontimestamp + "." + body. Kodieren Sie den Zeitstempel als ASCII; verwenden Sie die ursprünglichen Bytes des HTTP-Inhalts und das unveränderte Geheimnis aus der Übersicht als UTF-8. Dekodieren Sie das Geheimnis nicht als Base64.X-PatchFirst-Event: Ereignistyp, dereventim authentifizierten JSON entsprechen muss.X-PatchFirst-Delivery: Zustell-UUID, die der signiertenidentsprechen muss. Sie bleibt bei Wiederholungen gleich.
Lehnen Sie Zeitstempel ab, die mehr als 300 Sekunden vor oder nach der Empfängeruhr liegen, und vergleichen Sie Signaturen mit konstanter Laufzeit. Halten Sie Uhren synchronisiert. Prüfen Sie die ursprünglichen Bytes vor dem JSON-Parsen: Serialisierung, Leerraum- und Unicode-Änderungen machen die Signatur ungültig. Jeder neue Versuch erhält einen frischen Zeitstempel und eine neue Signatur; created bleibt unverändert.
Python 3
verify_webhook.py herunterladen. Nur Standardbibliothek. Übergeben Sie den ursprünglichen Anfrageinhalt als Bytes und die Header an receive_webhook.
Ausführbares Python-Beispiel anzeigen
"""PatchFirst receiver example (Python 3, standard library only).
Pass the original request bytes, before JSON parsing. The secret is the exact
UTF-8 string shown in the dashboard; do not base64-decode it.
"""
import hashlib
import hmac
import json
import re
import time
def verify_webhook(secret, body, headers, *, now=None):
"""Return the authenticated event or raise ValueError. No side effects."""
headers = {key.lower(): value for key, value in headers.items()}
timestamp = headers.get('x-patchfirst-timestamp', '')
signature = headers.get('x-patchfirst-signature', '')
if not re.fullmatch(r'[0-9]{1,12}', timestamp):
raise ValueError('Invalid timestamp')
if abs((time.time() if now is None else now) - int(timestamp)) > 300:
raise ValueError('Expired timestamp')
if not re.fullmatch(r'sha256=[0-9a-f]{64}', signature):
raise ValueError('Invalid signature format')
expected = 'sha256=' + hmac.new(
secret.encode('utf-8'), timestamp.encode('ascii') + b'.' + body,
hashlib.sha256).hexdigest()
if not hmac.compare_digest(expected, signature):
raise ValueError('Invalid signature')
event = json.loads(body)
if not isinstance(event, dict) or not isinstance(event.get('id'), str) or not event['id']:
raise ValueError('Missing delivery id')
if event['id'] != headers.get('x-patchfirst-delivery'):
raise ValueError('Delivery id mismatch')
if event.get('event') != headers.get('x-patchfirst-event'):
raise ValueError('Event mismatch')
return event
def receive_webhook(secret, body, headers, enqueue_unique, *, now=None):
"""Verify, then persist once. Return True for new, False for duplicate.
enqueue_unique(id, event) MUST atomically persist the event to a durable
queue with a UNIQUE constraint on id, returning False for an existing id.
Only return HTTP 2xx after this call succeeds. Queue consumers perform
business actions transactionally or with the same idempotency key.
"""
event = verify_webhook(secret, body, headers, now=now)
return enqueue_unique(event['id'], event)
Node.js
verify_webhook.js herunterladen. Nur integriertes node:crypto. Übergeben Sie den ursprünglichen Inhalts-Buffer an receiveWebhook vor jeder JSON-Parsing-Middleware.
Ausführbares Node.js-Beispiel anzeigen
'use strict';
// PatchFirst receiver example (Node.js, built-in modules only).
const {createHmac, timingSafeEqual} = require('node:crypto');
function verifyWebhook(secret, body, headers, now = Date.now() / 1000) {
// body must be the original Buffer, before JSON parsing.
const h = Object.fromEntries(Object.entries(headers).map(([k, v]) => [k.toLowerCase(), v]));
const timestamp = h['x-patchfirst-timestamp'];
const signature = h['x-patchfirst-signature'];
if (typeof timestamp !== 'string' || !/^[0-9]{1,12}$/.test(timestamp)) {
throw new Error('Invalid timestamp');
}
if (Math.abs(now - Number(timestamp)) > 300) throw new Error('Expired timestamp');
if (typeof signature !== 'string' || !/^sha256=[0-9a-f]{64}$/.test(signature)) {
throw new Error('Invalid signature format');
}
// Use the exact UTF-8 secret shown in the dashboard; do not base64-decode.
const digest = createHmac('sha256', Buffer.from(secret, 'utf8'))
.update(Buffer.from(timestamp + '.', 'ascii')).update(body).digest('hex');
const expected = Buffer.from('sha256=' + digest, 'ascii');
const received = Buffer.from(signature, 'ascii');
if (expected.length !== received.length || !timingSafeEqual(expected, received)) {
throw new Error('Invalid signature');
}
const event = JSON.parse(body.toString('utf8'));
if (!event || typeof event.id !== 'string' || !event.id) throw new Error('Missing delivery id');
if (event.id !== h['x-patchfirst-delivery']) throw new Error('Delivery id mismatch');
if (event.event !== h['x-patchfirst-event']) throw new Error('Event mismatch');
return event;
}
async function receiveWebhook(secret, body, headers, enqueueUnique, now = Date.now() / 1000) {
const event = verifyWebhook(secret, body, headers, now);
// enqueueUnique MUST atomically persist to a durable queue with UNIQUE(id).
// Return false for duplicates; return HTTP 2xx only after persistence succeeds.
// Queue consumers use transactions or the same idempotency key for effects.
return await enqueueUnique(event.id, event);
}
module.exports = {verifyWebhook, receiveWebhook};
Idempotenz und Empfängerantworten
Die Beispiele nehmen Ihre Funktion für eine dauerhafte Warteschlange entgegen, enqueue_unique(id, event) / enqueueUnique(id, event). Sie muss das authentifizierte Ereignis atomar mit einer Eindeutigkeitsbedingung auf id speichern und bei vorhandener id false liefern. Verwenden Sie dauerhaften Speicher statt einer Menge im Arbeitsspeicher. Bewahren Sie Schlüssel mindestens 48 Stunden auf. Warteschlangenverbraucher müssen Aktionen transaktional ausführen oder denselben Idempotenzschlüssel verwenden.
Antworten Sie mit 2xx nach erfolgreicher Speicherung oder erkanntem bereits gespeichertem Duplikat. Geben Sie bei ungültigen Signaturen 4xx und bei Speicherfehlern 5xx zurück. Bestätigen Sie kein Ereignis vor dauerhafter Speicherung. PatchFirst verwendet den Antwortinhalt nicht.
Zustellung und Grenzen
Ein separater Worker versendet Benachrichtigungen, ohne API-Anfragen zu verzögern. Unterschiedliche Empfänger werden gleichzeitig bedient, damit ein langsamer Empfänger andere Serverbestände nicht aufhält. Jede 2xx-Antwort gilt als erfolgreiche Zustellung. Vorübergehende Netzwerkfehler, TLS-Fehler einschließlich Zertifikatsprüfung und 5xx-Antworten lösen Wiederholungen nach 60, 120, 240 Sekunden aus, jeweils verdoppelt bis 21600 Sekunden (6 Stunden). Das Zustellfenster beträgt 86400 Sekunden (24 Stunden) ab Ereigniserstellung; danach wird kein weiterer Versuch geplant. Eine 410 Gone-Antwort beendet die Zustellung sofort; andere 4xx-Antworten erlauben insgesamt höchstens 3 Versuche. Eine 3xx-Antwort, unsichere Adresse oder übergroße Antwort beendet die Zustellung ohne Wiederholung. Duplikate sind möglich, die Reihenfolge ist nicht garantiert; die Zustellung ist kein garantierter Notfallkanal. Prüfen Sie nach ausgeschöpften Versuchen Protokoll und Empfänger.
Die Warteschlange eines Serverbestands umfasst höchstens 1000 ausstehende Zustellungen. Ist sie voll, werden die ältesten ausstehenden Ereignisse mit Protokolleintrag verworfen. Einstellungen können je Bestand höchstens 10-mal pro Minute gespeichert werden.
Weiterleitungen werden nie verfolgt. Zieladressen werden nach DNS-Auflösung und bei jeder Verbindung geprüft: private und reservierte IPv4-/IPv6-Adressen, Loopback, Link-Local, CGNAT und Metadatenadressen werden blockiert. Auch eigene Dienstadressen einschließlich öffentlicher Domain und IPs sind gesperrt. Localhost und ungewöhnliche numerische, oktale oder hexadezimale IPv4-Formen werden beim Speichern abgelehnt. Abgelehnte Adressen erscheinen im Protokoll nur als „nicht verfügbar“. Verwenden Sie einen öffentlichen Empfänger; es gelten 5 Sekunden Netzwerkzeitlimit, maximal 65536 Byte (64 KiB) Antwortgröße und Ratenbegrenzungen je Bestand. Eine 3xx-Antwort gilt als Fehler; ihr Ziel wird nie aufgerufen.
Unter Benachrichtigungen stehen letzte Zustellungen mit Zeitpunkt, Ereignis und gegebenenfalls HTTP-Antwortcode. Antwortinhalte werden nicht aufbewahrt. Prüfen Sie Übersicht und Empfängerzustand regelmäßig.