Hilfe

Eigenes System verbinden (Webhook)

Für jedes andere Website-System — Ihr Webentwickler richtet es einmal ein.

Stand: 8. Oktober 2026

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

  1. Adresse eintragen

    Öffnen Sie in Erstplatz Einstellungen → Autopilot-Einstellungen → Veröffentlichen, wählen Sie Webhook (JSON) und tragen Sie Ihre Webhook-Adresse ein.

  2. 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.

  3. 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.

  4. 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:

Inhalt von article.publish
{
  "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
  }
}
FeldBedeutung
eventarticle.publish für einen Artikel, ping für die Verbindungsprüfung (dann ohne article).
sentAtZeitpunkt des Versands (ISO 8601).
article.htmlDer fertige Artikel als HTML.
article.imagesBilder mit Adresse, Alternativtext und Quelle; Breite und Höhe, wenn bekannt.
article.schemaJsonLdStrukturierte Daten zum Artikel, fertig zum Einbinden.
article.externalIdnull 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.

verify.js (Node.js 18+)
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);
}
server.js — einfacher Empfänger
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 stellen

Sie 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 als externalId zurü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.