Dokumentation · Integrationen
Eigene Website anbinden (Webhook)
Beta. Diese Anbindung ist neu. Sie funktioniert, wird aber noch beobachtet — prüfe den ersten Beitrag bitte auf deiner Seite nach.
Noch nicht in der Auswahl? Diese Anbindung wird plattformweit freigeschaltet (Einstellung cms_family_enabled). Solange sie aus ist, zeigt die Seite CMS anbinden nur WordPress. Melde dich bei uns, wenn die Auswahl bei dir fehlt.
Wenn deine Website selbst gebaut ist — Next.js, Astro, Laravel, ein statischer Generator —, gibt es keine Schnittstelle, die wir kennen könnten. Also drehen wir es um: Du stellst eine Adresse bereit, wir schicken den fertigen Beitrag dorthin.
Einrichten
- Eine
https-Adresse bereitstellen, die JSON perPOSTentgegennimmt. - Ein Geheimnis ausdenken (mindestens 32 zufällige Zeichen) und an beiden Stellen hinterlegen: bei dir und hier im Formular.
- Verbindung testen klicken. Wir schicken ein
ping; antwortet deine Adresse mit2xx, steht die Verbindung.
Die Adresse muss öffentlich erreichbar sein. Adressen in privaten Netzen (localhost, 192.168.…, 10.…) lehnen wir ab — sie wären ein Weg in fremde Netze.
Die Signatur
Jede Nachricht trägt zwei Kopfzeilen:
X-Evnxt-Timestamp: 1770000000
X-Evnxt-Signature: sha256=4f3a… (Präfix `sha256=` plus 64 Hex-Zeichen)
Die Signatur ist ein sha256-HMAC über Zeitstempel.Rumpf mit deinem Geheimnis:
signatur = "sha256=" + hex( hmac_sha256( timestamp + "." + roher_koerper, secret ) )
Bei einer GET-Abfrage (Standsabfrage) ist der Rumpf leer; signiert wird dann timestamp + ".".
Prüfe die Signatur immer — sonst kann jeder, der deine Adresse kennt, Beiträge auf deiner Seite anlegen. Vergleiche zeitkonstant (hash_equals, crypto.timingSafeEqual) und weise Nachrichten ab, deren Zeitstempel mehr als fünf Minuten alt ist.
Es ist dieselbe Regel wie bei den ausgehenden Ereignis-Webhooks der Plattform. Wer beides nutzt, baut nur einen Prüfer.
Die Ereignisse
| Ereignis | Bedeutung |
|---|---|
ping |
Verbindungstest. Schreibt nichts. Darf ziele melden. |
article.draft |
Beitrag anlegen, nicht öffentlich. |
article.update |
Bestehenden Beitrag nachziehen. |
article.publish |
Beitrag öffentlich schalten. |
article.delete |
Entwurf entfernen (abgelehnter Beitrag). |
Beispiel eines Rumpfes:
{
"ereignis": "article.publish",
"zeit": "2026-02-01T10:15:00+01:00",
"website": { "id": 42, "name": "Magazin" },
"remote_id": "kunde-42",
"ziel": "ratgeber",
"beitrag": {
"id": 1234,
"titel": "Ein Beitrag",
"slug": "ein-beitrag",
"teaser": "Kurz und knapp erklärt.",
"body_html": "<p>Text</p>",
"bild_url": "https://beispiel.de/portal-media/titel.webp",
"meta_title": "Ein Beitrag",
"meta_description": "Kurz und knapp erklärt.",
"autor": "Redaktion",
"kategorie": "Ratgeber",
"veroeffentlicht_am": "2026-02-01T10:15:00+01:00"
}
}
Die Antwort
Antworte mit 2xx und diesem JSON:
{
"remote_id": "kunde-42",
"remote_url": "https://kunde.example/blog/ein-beitrag",
"remote_version": "2026-02-01T10:15:00Z"
}
remote_idist Pflicht. Ohne sie können wir den Beitrag später nicht mehr ansprechen — wir lehnen die Antwort dann ab.remote_urlist die öffentliche Adresse. Eine Empfangsbestätigung ist keine Veröffentlichung: Erst wenn du beiarticle.publisheine Adresse meldest, gilt der Beitrag bei uns als öffentlich.remote_versionist dein eigener Änderungsstand (Zeitstempel oder Hash). Meldest du ihn, erkennen wir, wenn jemand den Beitrag bei dir geändert hat, und überschreiben ihn nicht.
Stand abfragen
Vor jedem Schreibvorgang fragen wir den aktuellen Stand ab:
GET https://kunde.example/evnxt-hook?remote_id=kunde-42
Antworte mit remote_id, remote_url, remote_version und optional entwurf: true.
Fehler und Wiederholungen
Wir unterscheiden deine Antworten:
| Antwort | Bedeutung bei uns |
|---|---|
401 / 403 |
Zugangsfehler — wir fragen nach neuen Daten, keine Wiederholung. |
409 |
Konflikt — bei dir steht etwas Neueres. Wir überschreiben nichts. |
408, 429, 5xx, Zeitüberschreitung |
Vorübergehend — wir versuchen es später erneut. |
andere 4xx |
Endgültig abgelehnt — der Beitrag bleibt bei uns stehen und trägt deine Meldung. |
Wiederholungen sind ungefährlich: Wir schicken denselben Beitrag mit derselben remote_id. Solange du danach handelst statt blind anzulegen, entsteht kein Doppelpost.
Fertige Rezepte
Die Regeln auf dieser Seite gelten für jede Gegenstelle. Für drei häufige Fälle gibt es eine ausführliche Anleitung:
- Next.js anbinden — vollständiger Route Handler für den App Router, mit MDX- und Datenbank-Variante
- Framer anbinden — nur über einen Vermittlerdienst; was Framer heute erlaubt und was nicht
- WordPress.com anbinden — braucht diesen Weg nicht: ab Business läuft es über die native WordPress-Anbindung
Beispiel-Empfänger in PHP
<?php
$secret = getenv('EVNXT_SECRET');
$body = file_get_contents('php://input');
$zeit = $_SERVER['HTTP_X_EVNXT_TIMESTAMP'] ?? '';
$sig = $_SERVER['HTTP_X_EVNXT_SIGNATURE'] ?? '';
// 1. Zeitfenster
if (! ctype_digit($zeit) || abs(time() - (int) $zeit) > 300) {
http_response_code(401);
exit;
}
// 2. Signatur — zeitkonstant vergleichen
$erwartet = 'sha256=' . hash_hmac('sha256', $zeit . '.' . $body, $secret);
if (! hash_equals($erwartet, $sig)) {
http_response_code(401);
exit;
}
// 3. Verarbeiten
$daten = json_decode($body, true);
$beitrag = $daten['beitrag'] ?? [];
switch ($daten['ereignis'] ?? '') {
case 'ping':
echo json_encode(['ok' => true, 'ziele' => [['id' => 'ratgeber', 'title' => 'Ratgeber']]]);
exit;
case 'article.draft':
case 'article.update':
$id = speichere($beitrag, oeffentlich: false);
echo json_encode(['remote_id' => $id, 'remote_version' => gmdate('c')]);
exit;
case 'article.publish':
$id = speichere($beitrag, oeffentlich: true);
echo json_encode([
'remote_id' => $id,
'remote_url' => 'https://kunde.example/blog/' . $beitrag['slug'],
'remote_version' => gmdate('c'),
]);
exit;
case 'article.delete':
entferne($daten['remote_id']);
echo json_encode(['remote_id' => $daten['remote_id']]);
exit;
}
http_response_code(422);
echo json_encode(['message' => 'Unbekanntes Ereignis']);
Beispiel-Empfänger in Node (Express)
import express from 'express';
import crypto from 'node:crypto';
const app = express();
const secret = process.env.EVNXT_SECRET;
// Wichtig: den ROHEN Rumpf behalten — die Signatur gilt fuer genau diese Bytes.
app.use(express.raw({ type: 'application/json' }));
app.post('/evnxt-hook', (req, res) => {
const zeit = req.get('X-Evnxt-Timestamp') ?? '';
const sig = req.get('X-Evnxt-Signature') ?? '';
const body = req.body.toString('utf8');
if (!/^\d+$/.test(zeit) || Math.abs(Date.now() / 1000 - Number(zeit)) > 300) {
return res.sendStatus(401);
}
const erwartet = 'sha256=' + crypto.createHmac('sha256', secret).update(`${zeit}.${body}`).digest('hex');
const a = Buffer.from(erwartet, 'utf8');
const b = Buffer.from(sig, 'utf8');
if (a.length !== b.length || !crypto.timingSafeEqual(a, b)) {
return res.sendStatus(401);
}
const daten = JSON.parse(body);
switch (daten.ereignis) {
case 'ping':
return res.json({ ok: true, ziele: [{ id: 'ratgeber', title: 'Ratgeber' }] });
case 'article.draft':
case 'article.update': {
const id = speichere(daten.beitrag, false);
return res.json({ remote_id: id, remote_version: new Date().toISOString() });
}
case 'article.publish': {
const id = speichere(daten.beitrag, true);
return res.json({
remote_id: id,
remote_url: `https://kunde.example/blog/${daten.beitrag.slug}`,
remote_version: new Date().toISOString(),
});
}
case 'article.delete':
entferne(daten.remote_id);
return res.json({ remote_id: daten.remote_id });
}
return res.status(422).json({ message: 'Unbekanntes Ereignis' });
});
app.listen(3000);
Was diese Anbindung kann
| Fähigkeit | Webhook |
|---|---|
| Bilder in die Mediathek laden | nein — die Bildadressen zeigen auf uns; hol sie dir beim Speichern ab |
| Titel und Beschreibung für Suchmaschinen | ja — sie stehen in der Nachricht |
| Autorennamen setzen | ja — er steht in der Nachricht |
| Nicht öffentliche Entwürfe | ja — article.draft |
| Bestehende Beiträge ändern | ja — article.update |
| Fremde Änderungen erkennen | ja, sobald du remote_version meldest |
Zu den Bildern: Wir laden nichts zu dir hoch. Im body_html stehen Adressen unter /portal-media/…. Lade die Dateien beim Verarbeiten einmal herunter und lege sie bei dir ab — sonst hängt deine Seite dauerhaft an unserer.