So funktioniert es
Läuft Ihre Website auf einem System ohne eingebaute Verbindung, kann Erstplatz jeden Artikel an eine Adresse schicken, die Sie betreiben. Wir senden eine POST-Anfrage mit JSON, signiert mit einem Geheimnis, das nur Sie und Erstplatz kennen. Ihr Empfänger veröffentlicht den Artikel und antwortet mit dessen ID und öffentlicher Adresse.
- Die Adresse muss mit
https://beginnen, den Standard-Port 443 nutzen und auf eine öffentliche IP-Adresse zeigen. Private und lokale Adressen werden abgelehnt. - Weiterleitungen werden nicht verfolgt — tragen Sie die endgültige Adresse ein.
- Ihr Empfänger hat 20 Sekunden für die Antwort; sie darf höchstens 2 MB groß sein.
Einrichten
Adresse eintragen
Öffnen Sie in Erstplatz Einstellungen → Autopilot-Einstellungen → Veröffentlichen, wählen Sie Webhook (JSON) und tragen Sie Ihre Webhook-Adresse ein.
Speichern und Signaturgeheimnis kopieren
Klicken Sie auf Verbindung speichern. Erstplatz erzeugt ein Signaturgeheimnis und zeigt es nur einmal. Kopieren Sie es sofort in die Konfiguration Ihres Servers.
Empfänger bauen
Prüfen Sie bei jeder Anfrage die Signatur und antworten Sie wie unten beschrieben. Das Beispiel weiter unten ist ein vollständiger Ausgangspunkt in Node.js.
Verbindung prüfen
Klicken Sie auf Verbindung prüfen. Wir senden ein
ping-Ereignis; jeder 2xx-Status gilt als Erfolg.
Geheimnis verloren? Es lässt sich nicht noch einmal anzeigen. Klicken Sie auf Neues Signaturgeheimnis — das alte gilt sofort nicht mehr, passen Sie also gleich danach Ihren Server an.
Was wir senden
Kopfzeilen: Content-Type: application/json und X-Seo-Autopilot-Signature. Der Inhalt bei einem neuen Artikel:
{
"event": "article.publish",
"sentAt": "2026-10-07T14:03:22.512Z",
"article": {
"title": "Wie oft sollte man den Warmwasserspeicher spülen?",
"slug": "warmwasserspeicher-spuelen",
"html": "<p>…</p>",
"metaTitle": "Warmwasserspeicher spülen: wie oft?",
"metaDescription": "Ein Installateur erklärt …",
"images": [
{ "url": "https://…", "alt": "…", "source": "…", "width": 1200, "height": 800 }
],
"schemaJsonLd": { "@context": "https://schema.org", "@type": "Article" },
"externalId": null
}
}| Feld | Bedeutung |
|---|---|
event | article.publish für einen Artikel, ping für die Verbindungsprüfung (dann ohne article). |
sentAt | Zeitpunkt des Versands (ISO 8601). |
article.html | Der fertige Artikel als HTML. |
article.images | Bilder mit Adresse, Alternativtext und Quelle; Breite und Höhe, wenn bekannt. |
article.schemaJsonLd | Strukturierte Daten zum Artikel, fertig zum Einbinden. |
article.externalId | null bei einem neuen Artikel. Aktualisieren wir einen schon veröffentlichten Artikel, steht hier die id, die Sie zurückgegeben haben — aktualisieren Sie dann diesen Beitrag, statt einen neuen anzulegen. |
Signatur prüfen
Die Kopfzeile sieht so aus: t=1791381802,v1=5f2b…. t ist die Unix-Zeit in Sekunden, v1 der HMAC-SHA256 (hex) des Textes <t>.<roher Inhalt> mit Ihrem Signaturgeheimnis als Schlüssel. Rechnen Sie über die rohen Bytes, die Sie empfangen haben — JSON einlesen und neu schreiben verändert sie. Vergleichen Sie in konstanter Zeit und lehnen Sie alte Zeitstempel ab, damit niemand eine mitgeschnittene Anfrage wiederholen kann.
import { createHmac, timingSafeEqual } from 'node:crypto';
const TOLERANCE_SECONDS = 300; // reject requests older than 5 minutes
// rawBody: the request body exactly as received (a Buffer), before JSON.parse
export function verifySignature(rawBody, header, secret) {
if (typeof header !== 'string') return false;
const parts = Object.fromEntries(
header.split(',').map((part) => part.trim().split('=', 2)),
);
const t = Number(parts.t);
if (!Number.isInteger(t) || !/^[0-9a-f]{64}$/.test(parts.v1 ?? '')) return false;
if (Math.abs(Date.now() / 1000 - t) > TOLERANCE_SECONDS) return false;
const expected = createHmac('sha256', secret)
.update(`${t}.`)
.update(rawBody)
.digest();
const received = Buffer.from(parts.v1, 'hex');
return received.length === expected.length && timingSafeEqual(received, expected);
}import { createServer } from 'node:http';
import { verifySignature } from './verify.js';
const SECRET = process.env.ERSTPLATZ_SIGNING_SECRET;
createServer((req, res) => {
if (req.method !== 'POST') return res.writeHead(405).end();
const chunks = [];
req.on('data', (chunk) => chunks.push(chunk));
req.on('end', async () => {
const raw = Buffer.concat(chunks);
if (!verifySignature(raw, req.headers['x-seo-autopilot-signature'], SECRET)) {
return res.writeHead(401).end();
}
const body = JSON.parse(raw.toString('utf8'));
if (body.event === 'ping') {
return res.writeHead(200, { 'Content-Type': 'application/json' }).end('{"ok":true}');
}
if (body.event === 'article.publish') {
// Ihr CMS: Beitrag anlegen — oder aktualisieren, wenn body.article.externalId gesetzt ist.
const post = await savePost(body.article);
res.writeHead(200, { 'Content-Type': 'application/json' });
return res.end(JSON.stringify({ id: post.id, url: post.url }));
}
res.writeHead(400).end();
});
}).listen(3000); // hinter Ihren https-Server oder Proxy auf Port 443 stellenSie nutzen Express? Lesen Sie den Inhalt dieser Route mit express.raw({ type: 'application/json' }), damit req.body der rohe Buffer ist, über den die Signatur gebildet wurde.
Die Antwort, die wir erwarten
Auf article.publish antworten Sie mit einem 2xx-Status und JSON wie {"id": "123", "url": "https://example.com/blog/warmwasserspeicher-spuelen"}.
id: Text oder Zahl, 1–200 sichtbare ASCII-Zeichen. Wir schicken sie alsexternalIdzurück, wenn der Artikel aktualisiert wird.url: die öffentliche Adresse des Artikels (http oder https). Erstplatz zeigt sie als „Auf Ihrer Website ansehen“.- 401 oder 403 markiert die Verbindung als fehlgeschlagen — dann muss jemand nachsehen.
- Vorübergehende Probleme (Zeitüberschreitung, 429, 5xx) werden später automatisch wiederholt. Andere 4xx-Antworten markieren den Artikel mit dem Statuscode als fehlgeschlagen.
Häufige Fragen
Muss ich auf den Ping mit JSON antworten?
Nein. Die Verbindungsprüfung braucht nur einen 2xx-Status. Nur article.publish braucht die JSON-Antwort mit id und url.
Geht jeder Artikel an dieselbe Adresse?
Ja. Jeder Artikel geht an die eine Adresse, die Sie für diese Website gespeichert haben, signiert mit dem aktuellen Geheimnis.