evnxt.

Die Suche läuft in deinem Browser, es wird keine Eingabe an uns gesendet.

Dokumentation · Integrationen

Webhooks

Ein Webhook ist eine Adresse von dir, an die wir schicken, was in deinem Workspace passiert: ein Beitrag steht zur Prüfung, ein Beitrag ist erschienen, eine Keyword-Recherche ist durch. Du musst nicht pollen.

Beta, auf Anfrage freigeschaltet. Diese Seite ist immer sichtbar. Endpunkte anlegen kannst du unter Einstellungen → API, sobald die Produkt-API für deinen Workspace frei ist.

Endpunkt anlegen

Unter Einstellungen → API oder über die API:

curl -X POST https://app.evnxt.de/api/v1/webhooks \
  -H "Authorization: Bearer $EVX_KEY" \
  -H "Content-Type: application/json" \
  -d '{"url":"https://example.com/hooks/evnxt","events":["article.published","article.failed"]}'

Die Adresse muss https sein und öffentlich erreichbar. Adressen im privaten Netz (localhost, 10.*, 192.168.*, 169.254.*, …) werden abgelehnt — sonst wäre ein Webhook der bequemste Weg, unseren Server gegen fremde interne Dienste laufen zu lassen.

Die Antwort enthält das Secret genau einmal. Es ist der Schlüssel der Signatur; speichere es sicher, es wird nie wieder angezeigt.

Mit POST /webhooks/{id}/test schickst du ein ping-Ereignis an die Adresse. Das Ergebnis erscheint im Zustellprotokoll unter Einstellungen → API.

Ereignisse

Ereignis wann
article.created Ein Beitrag wurde angelegt; die Produktion beginnt.
article.ready_for_review Der Beitrag ist fertig produziert und liegt zur Freigabe.
article.approved Der Beitrag wurde freigegeben.
article.published Der Beitrag ist veröffentlicht (bei angebundenem CMS mit remote_url).
article.updated Eine überarbeitete Fassung wurde übernommen.
article.failed Die Veröffentlichung ist gescheitert; kind nennt die Art.
improvement.proposed Ein Verbesserungsvorschlag liegt im Posteingang.
keyword_research.completed Eine Keyword-Recherche ist durchgelaufen.
cms.connection_error Die tägliche Prüfung der CMS-Verbindung ist fehlgeschlagen.
ping Nur auf Anforderung über den Test-Button; nicht abonnierbar.

Wir senden ausschließlich Ereignisse, die dieser Endpunkt abonniert hat. Gibt es für ein Ereignis keinen aktiven Endpunkt, entsteht gar kein Zustellversuch.

Payload

Der Umschlag ist bei jedem Ereignis gleich; nur data unterscheidet sich.

{
  "event": "article.published",
  "created_at": "2026-09-15T09:41:02+02:00",
  "data": {
    "article_id": 481,
    "website_id": 12,
    "title": "Laufschuhe für breite Füße",
    "slug": "laufschuhe-fuer-breite-fuesse",
    "status": "publiziert",
    "remote_url": "https://laufschuhe.example/blog/laufschuhe-fuer-breite-fuesse",
    "published_at": "2026-09-15T09:41:00+02:00"
  }
}

Neue Felder können jederzeit hinzukommen. Ein Empfänger muss unbekannte Felder ignorieren.

Header Inhalt
X-Evnxt-Event Name des Ereignisses
X-Evnxt-Event-Id UUID des Ereignisses — bei jeder Wiederholung dieselbe
X-Evnxt-Timestamp Unix-Zeit des Sendeversuchs
X-Evnxt-Signature sha256=<HMAC>

Die Ereignis-ID ist dein Werkzeug gegen Doppelverarbeitung: Merke dir verarbeitete IDs und verwirf Wiederholungen. Eine Wiederholung trägt denselben Inhalt wie der erste Versuch — wir laden nichts nach.

Signatur prüfen

Signiert wird die Zeichenkette "<timestamp>.<body>" mit deinem Secret, per HMAC-SHA256.

PHP

$secret    = getenv('EVNXT_WEBHOOK_SECRET');
$body      = file_get_contents('php://input');
$timestamp = $_SERVER['HTTP_X_EVNXT_TIMESTAMP'] ?? '';
$signatur  = $_SERVER['HTTP_X_EVNXT_SIGNATURE'] ?? '';

$erwartet = 'sha256=' . hash_hmac('sha256', $timestamp . '.' . $body, $secret);

// hash_equals statt ==: ein einfacher Vergleich verrät über die Laufzeit,
// wie viele Zeichen gestimmt haben.
if (! hash_equals($erwartet, $signatur)) {
    http_response_code(401);
    exit;
}

// Alte Aufrufe verwerfen (Wiedereinspielung): mehr als fünf Minuten alt.
if (abs(time() - (int) $timestamp) > 300) {
    http_response_code(401);
    exit;
}

$ereignis = json_decode($body, true);
http_response_code(200);

Node.js

import crypto from 'node:crypto';

export function pruefe(rohKoerper, header, secret) {
  const zeit = header['x-evnxt-timestamp'] ?? '';
  const erwartet =
    'sha256=' + crypto.createHmac('sha256', secret).update(`${zeit}.${rohKoerper}`).digest('hex');
  const gesendet = header['x-evnxt-signature'] ?? '';

  const a = Buffer.from(erwartet);
  const b = Buffer.from(gesendet);

  if (a.length !== b.length || !crypto.timingSafeEqual(a, b)) return false;

  return Math.abs(Date.now() / 1000 - Number(zeit)) <= 300;
}

Wichtig: Signiert wird der rohe Körper, Zeichen für Zeichen. Wer erst JSON parst und neu serialisiert, bekommt eine andere Zeichenkette und damit eine andere Signatur.

Antwort, Wiederholungen und Abschaltung

Antworte mit einem beliebigen 2xx, sobald du die Nachricht angenommen hast. Verarbeite sie danach — eine langsame Antwort läuft nach 10 Sekunden in einen Zeitfehler und gilt als Fehlschlag.

Ist die Zustellung nicht erfolgreich, wiederholen wir sie:

Versuch Abstand zum vorherigen
2 1 Minute
3 5 Minuten
4 30 Minuten
5 2 Stunden
6 12 Stunden

Danach gilt die Zustellung als endgültig gescheitert (Status dead). Jede Wiederholung trägt dieselbe X-Evnxt-Event-Id und denselben Inhalt; nur Zeitstempel und Signatur sind neu.

Zehn Fehlschläge in Folge schalten den Endpunkt ab. Der Eigentümer des Workspaces bekommt eine Mail, offene Zustellungen laufen nicht weiter. Sobald deine Adresse wieder erreichbar ist, legst du den Endpunkt neu an. Eine erfolgreiche Zustellung setzt den Zähler zurück.

Das Zustellprotokoll der letzten 50 Versuche steht unter Einstellungen → API: Ereignis, Status, Anzahl Versuche, Antwortcode und der Zeitpunkt des nächsten Versuchs.

Was eine Empfangsbestätigung nicht ist

Dein 2xx sagt: „angekommen“. Es sagt nicht, dass auf deiner Seite etwas veröffentlicht wurde. Umgekehrt gilt dasselbe: article.published heißt, dass wir den Beitrag erfolgreich an das angebundene CMS übergeben haben — ob er dort öffentlich sichtbar ist, entscheidet dein CMS.

Screenshot folgtZustellprotokoll unter Einstellungen → API
Zur Übersicht Kostenlos starten Etwas fehlt oder stimmt nicht? Schreib an anfrage@wojcik.de.