PDNS Manager

Webhooks

Webhooks erlauben es, externe Systeme bei DNS-Änderungen automatisch zu benachrichtigen – z. B. einen ChatOps-Bot, ein internes Audit-Backend oder eine Backup-Pipeline, die nach jeder Zone-Änderung einen Snapshot zieht.

Webhook anlegen

Pro User unter Einstellungen → API & Sicherheit → Webhooks → Neuer Webhook:

  • Name – Anzeigename.
  • URL – HTTPS-Endpoint, der den POST entgegennimmt. Localhost / private IPs sind per Default blockiert (SSRF-Schutz, siehe unten).
  • Events – Welche Events triggern sollen. Default ["*"] = alles.

Nach dem Speichern wird das Shared Secret einmalig angezeigt – damit prüfst du auf Empfänger-Seite die Signatur.

Verfügbare Events

EventWann es feuert
zone.importedEine Zone wurde per Import erstellt oder ersetzt.
record.createdNeuer Record (per UI oder API).
record.updatedRecord-Inhalt oder TTL geändert.
record.deletedRecord gelöscht.
record.bulkBulk-Operation (mehrere Create/Delete in einem Call).

Payload

Body ist JSON. Beispiel für record.updated:

{
  "v": 1,
  "event": "record.updated",
  "timestamp": "2026-04-27T14:23:01.456Z",
  "app": "PDNS Manager",
  "actor_user_id": 17,
  "data": {
    "server": "master-fra1",
    "zone": "example.com.",
    "record": {
      "name": "www.example.com.",
      "type": "A",
      "ttl": 60,
      "old": [{"content": "203.0.113.10"}],
      "new": [{"content": "203.0.113.20"}]
    }
  }
}

Signaturprüfung (Pflicht!)

Jeder ausgehende Webhook-Request enthält den Header:

X-DNS-Manager-Signature: sha256=<hex>

Wert ist eine HMAC-SHA256-Signatur über den rohen JSON-Body-Bytes, signiert mit dem Webhook-Shared-Secret. Auf Empfänger-Seite immer prüfen, sonst kann jeder Skript-Kid einen gefälschten Webhook an deinen Endpoint schicken.

Verifikation in Python

import hmac, hashlib

SECRET = b"dein-webhook-secret"

def verify(raw_body: bytes, header_value: str) -> bool:
    if not header_value or not header_value.startswith("sha256="):
        return False
    sent = header_value.split("=", 1)[1].lower()
    calc = hmac.new(SECRET, raw_body, hashlib.sha256).hexdigest()
    return hmac.compare_digest(sent, calc)

# In FastAPI/Flask: raw_body = await request.body()

Verifikation in Node.js

import crypto from "node:crypto";

const SECRET = "dein-webhook-secret";

export function verify(rawBody, header) {
  if (!header?.startsWith("sha256=")) return false;
  const sent = header.slice("sha256=".length);
  const calc = crypto.createHmac("sha256", SECRET).update(rawBody).digest("hex");
  return crypto.timingSafeEqual(Buffer.from(sent, "hex"), Buffer.from(calc, "hex"));
}

SSRF-Schutz

Webhook-URLs werden vor dem Speichern und vor jedem Versand validiert. Standardmäßig blockiert:

  • localhost, 127.0.0.0/8
  • ::1
  • RFC1918 (10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16)
  • Link-Local (169.254.0.0/16, fe80::/10)
  • Multicast / Broadcast

Für interne Setups (z. B. ein Webhook gegen einen Internal-Service im selben Compose-Netz) gibt es einen Override:

# in der .env
WEBHOOK_ALLOW_PRIVATE_URLS=true

Retry-Verhalten

Webhooks laufen als Background-Task. Bei einem nicht-2xx-Antwortcode wird mit Backoff einige Male wiederholt. Dauerhafte Fehler landen im Backend-Log – im Panel siehst du in der Webhook-Liste den letzten Status.

Secret rotieren

In der Webhook-Liste ein Eintrag → Secret rotieren. Das alte Secret ist sofort ungültig, das neue wird einmalig angezeigt.