Webhooks machen jedes vorgelagerte System zu einem Publisher. Ein Payload signieren, per POST senden, und die Engine ordnet ihn Inhalten zu und baut neu — kein Schlüsselaustausch im Request-Pfad, denn die Signatur ist die Authentifizierung.

Der Endpunkt

POST https://your-host/webhooks/{integration}

{integration} ist ein Slug, den Sie beim Erstellen der Integration wählen — nicht der Name eines Mappers. Jede Integration hat einen eigenen Slug, ein eigenes Secret und einen zugehörigen Mapper, sodass eine Website mehrere haben kann: newsroom, docs-sync, partner-feed. Slugs bestehen aus Kleinbuchstaben, Ziffern und Bindestrichen.

php bin/fastsite webhook:create newsroom --site=blog --mapper=autoseo

Dies gibt einmalig die vollständige URL und das Signing-Secret aus. Dasselbe ist über das Dashboard, die REST-API oder das MCP-Tool create_webhook möglich — Webhooks sind site-gebunden, daher kann jedes Mitglied einer Website sie verwalten, ohne Administrator zu sein.

Eine Anfrage signieren

Der rohe Body wird unverändert eingelesen und verifiziert, bevor sonst etwas ausgeführt wird. Zwei Header sind erforderlich:

  • X-Fastsite-Signature: t=<unix>,v1=<hmac_sha256("<t>.<body>", secret)> — der signierte String besteht aus dem Zeitstempel, einem wörtlichen Punkt und den exakt übertragenen Bytes. Der Vergleich erfolgt in konstanter Zeit innerhalb eines Fensters von ±300 Sekunden.
  • X-Delivery-Id — eindeutig pro Zustellung. Dies ist der Replay-Schutz: Eine wiederholte ID wird mit einem 200 akzeptiert und stillschweigend ignoriert, anstatt doppelt verarbeitet zu werden.

Signieren Sie die Bytes, die Sie tatsächlich übertragen. Ein erneutes Serialisieren des JSON zwischen dem Signieren und dem Senden ist der häufigste Grund für eine Signatur, die nicht verifiziert werden kann.

Ein praktisches Beispiel

# body.json sind die exakten Bytes, die signiert und gesendet werden
SECRET="whsec_…"
BODY=$(cat body.json)
TS=$(date +%s)
SIG=$(printf '%s.%s' "$TS" "$BODY" | \
  openssl dgst -sha256 -hmac "$SECRET" | awk '{print $2}')

curl -X POST https://your-host/webhooks/newsroom \
  -H "X-Fastsite-Signature: t=$TS,v1=$SIG" \
  -H "X-Delivery-Id: $(uuidgen)" \
  -H "Content-Type: application/json" \
  --data-binary @body.json

Was zurückkommt

CodeBedeutung
202Akzeptiert. Aufgezeichnet und in die Warteschlange eingereiht.
200Doppelte Zustellungs-ID — bereits bekannt, keine Aktion.
400Fehlender X-Delivery-Id.
401Signatur fehlt oder ist ungültig.
404Integration nicht vorhanden oder deaktiviert.

Der Endpunkt antwortet in Millisekunden, da er nur aufzeichnet und einreiht. Die Zuordnung, der Build und die Veröffentlichung erfolgen anschließend in der Warteschlange, mit Wiederholungsversuchen und Backoff.

Webhooks veröffentlichen; die API nicht

Dies ist der einzige wesentliche Unterschied zwischen den beiden Ingest-Pfaden. Inhalte über REST oder MCP zu erstellen speichert sie und wartet darauf, dass Sie sie veröffentlichen. Eine Webhook-Zustellung führt den Mapper aus und löst anschließend automatisch einen Build und eine Veröffentlichung aus — ein vorgelagertes System, das einen Artikel pusht, erwartet, dass dieser live ist und nicht nur bereitgestellt.

Integrierte Mapper

Mapper sind Code, keine Konfigurationsstrings. Jeder nimmt einen generischen Payload entgegen und wandelt ihn in eine Engine-Aktion um.

MapperPayloadFunktion
autoseo{id, title, body_html, slug?, excerpt?, featured_image_url?, author?, seo_title?, seo_description?, status?, published_at?}Idempotenter Artikel-Ingest, mit id als Schlüssel.
create_post{title, body_html, slug?, excerpt?, status?, author?, source_ref?}Ordnet einen Payload einem Beitrag zu.
create_pageWie obenOrdnet einen Payload einer Seite zu.
import_media{url, alt?, decorative?}Lädt eine Datei per URL (SSRF-gesichert) in die Bibliothek.
upsert_component{handle, type, source_code, css?}Erstellt oder aktualisiert eine Komponente.

autoseo ist der bevorzugte Mapper für eine Artikel-Pipeline. Er ist idempotent auf die id des Payloads, sodass eine erneute Zustellung den vorhandenen Beitrag aktualisiert, anstatt einen zweiten zu erstellen. Er übernimmt auch Bilder: Das Featured Image und alle externen <img>-Elemente im Body werden in Ihre Medienbibliothek geladen und in lokale Referenzen umgeschrieben, mit Alt-Text aus dem Titel, wenn die Quelle keinen angibt. Kann ein Bild nicht geladen werden, wird es weggelassen und der Artikel wird trotzdem veröffentlicht — ein ausgefallenes CDN upstream soll Ihnen nicht die Geschichte kosten.

Bei den Inhalts-Mappern ist der Standardwert für status published. Übergeben Sie source_ref, wenn eine erneute Zustellung aktualisieren soll, anstatt zu duplizieren.

Wenn etwas schiefläuft

Jede Zustellung wird mit ihren Headern, dem Body und dem Ergebnis aufgezeichnet, sodass ein Fehler nachvollziehbar und nicht verloren ist.

php bin/fastsite webhook:list
php bin/fastsite webhook:deliveries --limit=20
php bin/fastsite webhook:replay <delivery-id>

Ein Payload, den der Mapper nicht verwenden kann — eine fehlende id, kein title, fehlerhaftes JSON — wird als skipped markiert und nicht erneut versucht, da ein Wiederholungsversuch daran nichts ändern würde. Alles andere, das einen Fehler wirft, wird als failed markiert und mit Backoff erneut versucht. Replay führt eine gespeicherte Zustellung aus ihren aufgezeichneten Bytes erneut aus, sodass Sie einen Mapper oder eine Berechtigung korrigieren und erneut verarbeiten können, ohne den Sender um einen erneuten Versuch zu bitten.

Secrets werden verschlüsselt gespeichert und können im Dashboard erneut angezeigt werden, um einen Sender zu konfigurieren. Möchten Sie lieber abrufen, als empfangen zu werden? Die REST-API und der MCP-Server decken dieselben Vorgänge ab.