OXIRANKER DEVELOPER-DOKUMENTATION

OxiRanker-Dokumentation

Ein übersichtliches Dokumentationszentrum zur Verbindung deiner Website mit OxiRanker-Diensten. Beginne mit der WordPress-Integration oder der API-Integration und folge anschließend der Anleitung Schritt für Schritt.

Offizielle DokumentationVollständige V1 mit allen FunktionenFür Website-Betreiber, Entwickler und Integrationsteams
Dokumentationsmenü
Wähle eine Anleitung aus dem Menü. Diese Seite ist das zentrale Dokumentationszentrum für OxiRanker.
API-Dienst

API-Dienst

Nutze Export API und Webhook für individuelle Websites, eigene CMS-Plattformen, Next.js-, Laravel-, Node.js-, PHP- oder Python-Backends.

Domain
Verifiziert
Inhalt
Bereit
API
Webhook
Wichtiger Hinweis zur Speicherung

OxiRanker ist kein dauerhaftes Hosting für die Zielwebsite

OxiRanker bietet keine Garantie für dauerhaftes Hosting von Inhalten, Bildern oder exportierten Dateien der Zielwebsite. OxiRanker ist kein Hosting-Dienst für Benutzerinhalte, und generierte Inhalte, Titelbilder, Open-Graph-Bilder oder Ausgabedateien bleiben möglicherweise nicht länger als 90 Tage auf der OxiRanker-Infrastruktur.

Deshalb muss die Zielwebsite nach dem Empfang der Ausgabe den finalen Artikel, Bilder, SEO-Daten, Open-Graph-Daten und Schema-Daten in ihrer eigenen Datenbank, auf ihrem eigenen Server, in ihrem eigenen Speicher oder CDN speichern und darf sich für die öffentliche Anzeige nicht auf OxiRanker-URLs verlassen.

Schulungsvideo

Schulungsvideo zur Verbindung

Nutze dieses Video als visuelle Schritt-für-Schritt-Anleitung, bevor du WordPress verbindest oder die API-Integration implementierst.

API-Ausgabe

Implementiere den Ausgabeablauf Schritt für Schritt

Speichere zuerst den Export Token und das Webhook Secret in deinem Backend und vervollständige anschließend Webhook Receiver, Datenbank, UPSERT-Logik, Image Mirroring und tägliche Synchronisierung.

SCHRITT 01

Registrieren und eine Domain in OxiRanker erstellen

Registriere dich zuerst bei OxiRanker, melde dich in deinem Konto an und klicke im Bereich Domains auf Add Domain.

Richtiges Domainformat

Gib im Feld Domain name nur die Root-Domain ein. Die Adresse darf weder http noch https noch www enthalten.

example.com

Falsche Formate

Gib diese Formate nicht in das Domainfeld ein.

https://example.com
http://example.com
www.example.com
Nach dem Erstellen der Domain
Wenn das Domainformat gültig ist, erstellt OxiRanker die Domain und führt dich zum Schritt zur Verifizierung der Domaininhaberschaft.
SCHRITT 02

Domaininhaberschaft verifizieren

In diesem Schritt musst du nachweisen, dass die Domain dir gehört oder dass du Zugriff auf ihre Verwaltung hast.

DNS TXT

Empfohlen

Füge einen TXT-Record zur Domain-DNS hinzu. Bei Cloudflare ist dies normalerweise die einfachste Methode.

DNS Name:
_yourdomain-verification.example.com

TXT Value:
acb1****29803

HTTP File

Lege eine Textdatei im .well-known-Pfad deiner Website ab, damit OxiRanker sie verifizieren kann.

File URL:
https://example.com/.well-known/yourdomain-verification.txt

File Content:
220daaa37****42730f27

Meta Tag

Füge ein Meta-Tag in den head-Bereich der Startseite deiner Website ein.

<meta name="yourdomain-verification" content="190208d****d275b65" />
Wichtiger DNS-Hinweis
Nach dem Hinzufügen des DNS-Records musst du besonders bei Cloudflare möglicherweise 2 bis 3 Minuten warten. Klicke anschließend auf derselben OxiRanker-Seite auf Confirm.
Gültigkeitsdauer des Verifizierungstokens
Werte zur Domainverifizierung haben ein Zeitlimit. Derzeit sind Verifizierungswerte normalerweise 1440 Minuten gültig.
SCHRITT 03

Kostenlose Token nach Verifizierung der ersten Domain erhalten

Nach der Verifizierung der ersten Domain fügt OxiRanker deinem Konto 1000 kostenlose Token für erste Tests hinzu.

Nur für die erste Domain
Dieses Geschenk wird nur für die erste verifizierte Domain hinzugefügt und nicht erneut für spätere Domains vergeben.
SCHRITT 04

Sprachen und Blog-URL konfigurieren

Lege im Bereich Blog URLs fest, ob deine Website einsprachig oder mehrsprachig ist und welche Sprachen für generierte Artikel verwendet werden sollen.

Sprachen auswählen

OxiRanker unterstützt 24 Sprachen. Du kannst je nach Struktur deiner Website eine oder mehrere Sprachen auswählen.

Blog-URL eingeben

Gib für jede Sprache die Blog-URL dieser Sprache ein. Wenn deine Website mehrsprachig ist, gib jeden Sprachpfad separat ein.

https://example.com/fa/blog
https://example.com/en/blog
URL-Struktur
Wenn deine Website eine andere Struktur hat, gib die echte Blog-URL deiner Website ein. Wichtig ist, dass die finale URL der Pfad ist, unter dem Artikel veröffentlicht werden sollen.
SCHRITT 05

Keywords für jede Sprache hinzufügen

Füge im Tab Keywords für jede Sprache mindestens 3 und höchstens 50 Keywords hinzu.

Hauptregel

Keywords für jede Sprache müssen in derselben Sprache eingegeben werden. Wenn die Artikelsprache Englisch ist, müssen auch die Keywords Englisch sein.

So fügst du Keywords hinzu

Drücke nach dem Eingeben jedes Keywords Enter, um es hinzuzufügen. Wenn alle Keywords vollständig sind, klicke auf Save.

Häufiger Fehler
Wenn du türkische Keywords für die englische Sprache eingibst, können türkische Phrasen in englischen Inhalten erscheinen. Die Keyword-Sprache muss zur Artikelsprache passen.
SCHRITT 06

Das Domain Article Profile vervollständigen

Das Article Profile teilt OxiRanker mit, was deine Marke ist, für wen geschrieben wird, welchen Ton es verwenden soll und welche Themen vermieden werden sollen.

Branche

Definiert den Markt oder die Geschäftskategorie. Beispiel: Immobilienberatung sowie Kauf und Verkauf von Immobilien in Dubai.

Geschäftsbeschreibung

Erklärt, was das Unternehmen verkauft, wen es bedient, wie es funktioniert und was es besonders macht.

Zielgruppe

Definiert, wen die Inhalte ansprechen sollen. Jede Zielgruppe kann separat gespeichert werden.

Ton

Steuert Stil und Stimme der generierten Inhalte, zum Beispiel formell, lehrreich, einfach, technisch oder verkaufsorientiert.

Inhaltsziel

Definiert, was die Artikel erreichen sollen, zum Beispiel Kunden gewinnen, Traffic steigern oder Nutzer informieren.

Verbotene Themen

Alles, worüber die Marke nicht sprechen oder womit sie nicht in Verbindung gebracht werden soll, wird hier eingetragen.

Optionaler CTA

CTA bedeutet die Handlung, die der Leser nach dem Lesen des Artikels ausführen soll. Wenn du nicht sicher bist, welcher Text geeignet ist, ist es besser, dieses Feld leer zu lassen.

Mehrsprachiges Profil
Wenn deine Website mehrsprachig ist, musst du das Profil nur in einer Sprache ausfüllen. OxiRanker kann die benötigten Versionen für andere Sprachen auf Basis desselben Profils vorbereiten.
SCHRITT 07

Interne und externe Verlinkung

OxiRanker kann interne und externe Links intelligent innerhalb von Artikeln anwenden.

Interne Links

Diese Option ist standardmäßig aktiviert und sollte am besten aktiviert bleiben. Interne Verlinkung hilft Suchmaschinen, die Inhaltsstruktur deiner Website besser zu verstehen.

Im ersten Artikel werden interne Links normalerweise nicht erstellt, weil noch kein vorheriger Artikel vorhanden ist. Ab dem zweiten Artikel kann das System interne Verlinkung durchführen.

Externe Links

Externe Links werden zu offiziellen und vertrauenswürdigen Quellen hinzugefügt und als nofollow markiert, damit SEO-Autorität nicht unbeabsichtigt übertragen wird.

SCHRITT 08

Profilqualität mit AI prüfen

Nach dem Ausfüllen des Article Profile überprüft OxiRanker die Profilqualität.

85+
Mindestpunktzahl zum Speichern
95
Sehr gute Punktzahl, die nach Verbesserungen häufig erreicht wird
AI
Professionelle Vorschläge für jedes Feld
Wenn die Punktzahl unter 85 liegt
Das System erlaubt kein endgültiges Speichern und zeigt Erklärungen sowie Verbesserungsvorschläge für jedes problematische Feld an. Du kannst die Vorschläge übernehmen und erneut speichern.
SCHRITT 09

Telegram für Artikelzusammenfassungen verbinden

Nach erfolgreichem Speichern des Profils kannst du den Versand von Artikelzusammenfassungen an einen Telegram-Kanal aktivieren.

1

Bot hinzufügen

Füge @Oxiranker_Bot zu deinem Telegram-Kanal hinzu und mache ihn zum Admin.

2

Kanal-Benutzernamen eingeben

Gib den Kanalnamen mit @ ein. Beispiel: @my_channel

3

Zusammenfassungsversand aktivieren

Aktiviere nach der Registrierung eines gültigen Kanals den Versand von Artikelzusammenfassungen.

Telegram-Vorteil
Wenn aktiviert, wird nach der Inhaltserstellung die Artikelzusammenfassung mit Bild und Link an den Telegram-Kanal gesendet. Das kann die Traffic-Wege zu deiner Website stärken.
SCHRITT 10

Die automatische Artikel-Engine konfigurieren

Im Tab Automatic article generation kannst du die automatische Artikel-Engine verwalten.

Intelligente Themenauswahl

Das System nutzt Keywords und Article Profile, um passende, einzigartige und veröffentlichungsfähige Themen auszuwählen.

Markenbewusste Generierung

Ton, Zielgruppe, Inhaltsziel und Markenbeschränkungen werden während der Artikelerstellung berücksichtigt.

Geplante Veröffentlichung

Die monatliche Kapazität wird über den Monat verteilt, damit die Veröffentlichung von Inhalten natürlicher und konsistenter wirkt.

Monatliche Kapazität

7 articles / month
10 articles / month
15 articles / month
30 articles / month

30 Artikel bedeuten fast einen Artikel pro Tag. 15 Artikel bedeuten fast jeden zweiten Tag. 10 Artikel bedeuten etwa alle zwei bis drei Tage. 7 Artikel bedeuten etwa alle drei bis vier Tage.

Artikellänge und Bild

Die Artikellänge kann Short, Standard oder Long sein. Standard ist für die meisten Websites eine ausgewogene Wahl.

Artikelbilder können aktiviert oder deaktiviert werden, und das Bilddesign kann Light oder Dark sein.

Hinweis für API-Websites
Die automatische Engine generiert Artikel innerhalb von OxiRanker. Um jeden Artikel nach der Generierung auf deine individuelle Website zu übertragen, musst du Export API, Webhook, Datenbankspeicherung und Image Mirroring implementieren.
SCHRITT 11

Den ersten Artikel über Generate erstellen

Nachdem die Domain vorbereitet ist, öffne das Menü Generate, wähle die Domain aus und klicke auf New Content oder New Article.

1

Sprache

Für jede Inhaltsanfrage wird nur eine Sprache ausgewählt.

2

Thema

Schreibe ein klares Thema. Source URL ist optional.

3

Bild

Aktiviere oder deaktiviere das Bild und wähle das Design Light oder Dark.

4

Zahlung und Generierung

Die Kosten werden vom Wallet abgezogen und die Artikelerstellung startet.

Generierungszeit
Die Generierung von Artikel und Bild dauert normalerweise etwa 2 bis 3 Minuten. Nach Abschluss ist der Artikel in OxiRanker sichtbar.
Source URL
Wenn eine Source URL eingegeben wird, nutzt OxiRanker sie, um das Thema besser zu verstehen, erstellt aber keine kopierten Inhalte. Das System ist dafür entwickelt, einzigartige Inhalte zu erstellen.
SCHRITT 12

Preview, SEO Report und JSON Output prüfen

Nach der Generierung des Artikels stehen mehrere wichtige Ausgaben zur Qualitätsprüfung und Vorbereitung der Veröffentlichung zur Verfügung.

Preview

Zeigt den Artikeltext und das generierte Bild. Dieser Bereich ist hilfreich, um die finale Qualität vor der Veröffentlichung zu prüfen.

SEO Report

Zeigt einen verständlichen SEO-Bericht mit SEO Score, title, meta description, canonical, Keywords, schema, Überschriften, Links, Bildern und OpenGraph.

JSON Output

Zeigt die vollständige Artikelausgabe für technische Nutzer, einschließlich slug, html_body, schema, Bild-URLs, Tags, Kategorie, Lesezeit und Erstellungs-/Aktualisierungszeitpunkten.

Bedeutung der Ausgaben
Diese Ausgaben zeigen, dass der Artikel nicht nur einfacher Text ist. Er ist strukturierter, SEO-fertiger Inhalt, vorbereitet für WordPress, CMS, API und Veröffentlichungssysteme.
SCHRITT 13

Was genau machen Export API und Webhook?

Nachdem ein Artikel in OxiRanker generiert wurde, kann die Zielwebsite ihn über Export API lesen oder per Webhook benachrichtigt werden, wenn der Artikel bereit ist.

Methode 1: Export API

Die Zielwebsite kann Artikel bei Bedarf mit dem Export Token aus OxiRanker lesen. Diese Methode wird für manuelle Synchronisierung, tägliche Synchronisierung, Artikellistenseiten und das Abrufen des vollständigen Artikel-JSON verwendet.

GET https://oxiranker.com/api/v1/users/domains/1/export/articles?limit=20&offset=0
Authorization: Bearer YOUR_EXPORT_TOKEN
Accept: application/json
GET https://oxiranker.com/api/v1/users/domains/1/articles/5/export/json
Authorization: Bearer YOUR_EXPORT_TOKEN
Accept: application/json

Methode 2: Webhook

Wenn ein neuer Artikel erstellt oder ein Artikel aktualisiert wird, benachrichtigt OxiRanker den Endpunkt der Zielwebsite. Webhook sendet nicht den vollständigen Artikel. Er sendet nur die Artikelkennung, damit die Zielwebsite den vollständigen Artikel aus der Export JSON API abrufen kann.

https://example.com/api/oxiranker/webhook
{
  "event": "article.created",
  "domain_id": "1",
  "article_id": "5",
  "request_id": "5",
  "lang": "fa",
  "slug": "smart-internal-links",
  "occurred_at": "2026-05-20T19:35:33.000Z"
}
Sehr wichtiger Hinweis
Webhook ist nur eine leichte Nachricht. Die Zielwebsite darf den vollständigen Artikel nicht im Webhook erwarten. Nach Empfang von domain_id und article_id muss sie den vollständigen Artikel aus der Export JSON API abrufen und in ihrer eigenen Datenbank speichern.
SCHRITT 14

Verantwortung für Inhaltsspeicherung und Image Mirroring

Die Zielwebsite muss den finalen Artikel und die Bilder auf ihrer eigenen Infrastruktur speichern.

Was die Zielwebsite speichern muss

Vollständiger Artikeltext in der Datenbank der Zielwebsite
Hauptbild des Artikels auf dem Zielserver, im Speicher oder CDN
Open-Graph-Bild auf dem Zielserver, im Speicher oder CDN
SEO-, OpenGraph- und Article-Schema-Daten
raw_detail_payload und raw_list_item zur Bewahrung der ursprünglichen Ausgabe

Beispiel für einen richtigen Bildpfad

Die direkte und dauerhafte Verwendung von OxiRanker-Bild-URLs ist nicht korrekt. Das Bild muss heruntergeladen werden und Artikel-URLs müssen durch interne URLs der Zielwebsite ersetzt werden.

https://oxiranker.com/uploads/articles/3_5/cover.webp
https://example.com/uploads/site-articles/oxiranker/1/5/cover.webp
OxiRanker ist kein dauerhaftes Hosting für die Zielwebsite
OxiRanker ist nicht dafür verantwortlich, Ausgabedaten und Bilder der Zielwebsite dauerhaft zu speichern. OxiRanker ist kein Hosting-Dienst für Benutzerinhalte, und generierte Inhalte, Titelbilder, Open-Graph-Bilder oder Ausgabedateien bleiben möglicherweise nicht länger als 90 Tage auf der OxiRanker-Infrastruktur. Deshalb muss die Zielwebsite den finalen Artikel, Bilder, SEO-Daten, OpenGraph und Schema in ihrer eigenen Datenbank, auf ihrem eigenen Server, in ihrem Speicher oder CDN speichern.
SCHRITT 15

Export Token und einsatzbereite Endpunkte erhalten

Export Token und einsatzbereite Endpunkte sind auf der Artikelseite, im Outputs-Tab und im Bereich API Output verfügbar.

Wo du den Export Token erhältst

Erstelle den ersten Artikel in OxiRanker.
Öffne diese Artikelseite.
Öffne den Outputs-Tab.
Öffne den Bereich API Output.
Erstelle einen Token über Token management.
Der vollständige Token wird nur einmal angezeigt.
Speichere den Token im Backend oder in der .env der Zielwebsite.
https://oxiranker.com/en/pannel/generate/{domainId}/articles/{articleId}

Einsatzbereite Endpunkte

Auf derselben Artikelseite zeigt Ready-to-use endpoints im Bereich API Output vorbereitete URLs an.

https://oxiranker.com/api/v1/users/domains/1/export/articles?limit=20&offset=0
https://oxiranker.com/api/v1/users/domains/1/articles/1/export/json
https://oxiranker.com/api/v1/users/domains/1/articles/1/export/html?canonical_mode=absolute&include_css=0
Token rotieren
Wenn der Export Token im OxiRanker-Panel rotiert wird, läuft der vorherige Token ab. Der neue Wert muss den alten Wert sofort in der .env oder Backend-Umgebung der Zielwebsite ersetzen, und das Ziel-Backend muss neu gestartet oder neu bereitgestellt werden.
SCHRITT 16

Webhook Secret und Ziel-Endpunkt erstellen

Webhook Secret wird ebenfalls auf der Artikelseite, im Outputs-Tab und im Bereich API Output erstellt.

Wo wird Webhook Secret erstellt?

Öffne die Artikelseite.
Öffne den Outputs-Tab.
Öffne den Bereich API Output.
Erstelle ein Secret im Bereich Webhook Secret.
Das vollständige Secret wird nur einmal angezeigt.
Speichere das Secret in der .env der Zielwebsite.

Fester Ziel-Endpunkt

OxiRanker akzeptiert für den Ziel-Webhook nur den folgenden festen Pfad. Die Zielwebsite muss diesen Endpunkt in ihrem Backend erstellen.

POST /api/oxiranker/webhook

https://example.com/api/oxiranker/webhook

Dieser Endpunkt darf nur POST akzeptieren, muss den raw body beibehalten, timestamp und signature verifizieren und für webhook.test eine erfolgreiche Antwort zurückgeben.

Aktive Events
In der aktuellen Version sind Events fest definiert: article.created und article.updated. Zum Testen der Verbindung wird der Event-Wert webhook.test gesendet.
SCHRITT 17

Erforderliche .env-Werte für die Zielwebsite

OxiRanker hat keinen Zugriff auf die Dateien oder die .env der Zielwebsite. Der Entwickler der Zielwebsite muss diese Werte im eigenen Backend speichern.

OXIRANKER_EXPORT_API_BASE_URL=https://oxiranker.com
OXIRANKER_EXPORT_API_TOKEN=YOUR_EXPORT_TOKEN
OXIRANKER_WEBHOOK_SECRET=YOUR_WEBHOOK_SECRET
OXIRANKER_WEBHOOK_MAX_SKEW_MS=300000
OXIRANKER_SOURCE_PROVIDER=oxiranker
OXIRANKER_SOURCE_DOMAIN_IDS=1

SITE_ARTICLE_IMAGE_UPLOAD_DIR=/var/www/example.com/uploads/site-articles
SITE_ARTICLE_IMAGE_MAX_BYTES=10485760

PUBLIC_BASE_URL=https://example.com

OXIRANKER_SYNC_ENABLED=true
OXIRANKER_SYNC_HOUR=3
OXIRANKER_SYNC_MINUTE=10
OXIRANKER_SYNC_PAGE_LIMIT=20
OXIRANKER_SYNC_MAX_PAGES=50

Sicherheitswerte

OXIRANKER_EXPORT_API_TOKEN
Der Export-API-Token darf nur im Backend gespeichert werden.
OXIRANKER_WEBHOOK_SECRET
Das Webhook Secret darf nur im Backend gespeichert werden.
OXIRANKER_WEBHOOK_MAX_SKEW_MS
Der maximal erlaubte Zeitunterschied für Webhook. 300000 bedeutet 5 Minuten.

Speicherwerte

SITE_ARTICLE_IMAGE_UPLOAD_DIR
Der physische Pfad zum Speichern gespiegelter Bilder auf dem Zielserver.
SITE_ARTICLE_IMAGE_MAX_BYTES
Die maximal erlaubte Bildgröße für Download und Speicherung.
PUBLIC_BASE_URL
Die öffentliche URL der Zielwebsite, die zur Erstellung finaler Bild- und Artikel-URLs verwendet wird.
Frontend-Sicherheit
Export Token und Webhook Secret dürfen nicht in Frontend-Code, benutzersichtbarem JavaScript, localStorage oder dem window-Objekt abgelegt werden.
SCHRITT 18

Export API: List, JSON, HTML und ZIP

Die Zielwebsite kann Export API verwenden, um die Artikelliste oder die vollständige Ausgabe jedes Artikels zu erhalten.

Artikelliste für eine Domain abrufen

GET https://oxiranker.com/api/v1/users/domains/:domainId/export/articles?limit=20&offset=0
Authorization: Bearer YOUR_EXPORT_TOKEN
Accept: application/json

limit definiert die Anzahl der Artikel und ist derzeit auf 1 bis 50 begrenzt. offset wird für Pagination verwendet. lang ist optional und wird verwendet, um Artikel für eine bestimmte Sprache wie fa, en oder tr abzurufen.

Den vollständigen Artikel als JSON abrufen

GET https://oxiranker.com/api/v1/users/domains/:domainId/articles/:articleId/export/json
Authorization: Bearer YOUR_EXPORT_TOKEN
Accept: application/json

Dies ist die wichtigste Ausgabe für die Zielwebsite. Die Zielwebsite muss das vollständige Artikel-JSON in ihrer eigenen Datenbank speichern und für die öffentliche Anzeige aus ihrer eigenen Datenbank lesen.

Fertiges HTML abrufen

GET https://oxiranker.com/api/v1/users/domains/1/articles/5/export/html?canonical_mode=absolute&include_css=0
Authorization: Bearer YOUR_EXPORT_TOKEN

HTML eignet sich für Websites, die eine fertige Ausgabe möchten. Der professionellere Ansatz ist jedoch, JSON zu speichern und es mit dem eigenen Template der Zielwebsite zu rendern.

ZIP-Ausgabe abrufen

GET https://oxiranker.com/api/v1/users/domains/1/articles/5/export/zip
Authorization: Bearer YOUR_EXPORT_TOKEN

Die ZIP-Ausgabe enthält article.json, meta.json, assets.json, schema.jsonld, index.html, version.txt und README.txt.

SCHRITT 19

Webhook-Signatur und sicherer Webhook-Empfang

Die Zielwebsite muss die Webhook-Signatur mit dem ursprünglichen raw body verifizieren.

Webhook-Header

content-type: application/json
user-agent: Oxiranker-Webhooks/1.0
x-oxiranker-event: article.created
x-oxiranker-domain-id: 1
x-oxiranker-timestamp: 2026-05-20T19:35:33.000Z
x-oxiranker-signature: sha256=...

Signaturformel

payload_to_sign = timestamp + "." + rawBody
signature = "sha256=" + HMAC_SHA256(webhook_secret, payload_to_sign)

Wenn die Zielwebsite JSON zuerst parst und anschließend erneut stringifiziert, können sich Reihenfolge oder Abstände ändern und die Signatur ungültig werden. Für die Signaturverifizierung muss der ursprüngliche raw body verwendet werden.

Timing-sicherer Vergleich
Vergleiche Signaturen nicht mit expected === received. In Node.js ist es besser, crypto.timingSafeEqual zu verwenden.
SCHRITT 20

Einfaches Node.js-Codebeispiel zum Empfangen von Webhook

Dieses Express-Beispiel verarbeitet raw body, timestamp, signature, webhook.test und Artikel-Events.

import express from "express";
import crypto from "crypto";

const app = express();

app.use(
  express.json({
    verify: (req, _res, buf) => {
      (req as any).rawBody = Buffer.from(buf);
    },
  })
);

function getHeader(req: express.Request, name: string): string {
  const value = req.headers[name.toLowerCase()];
  if (Array.isArray(value)) return String(value[0] || "").trim();
  return String(value || "").trim();
}

function getRawBody(req: express.Request): string {
  const raw = (req as any).rawBody;
  if (Buffer.isBuffer(raw)) return raw.toString("utf8");
  return JSON.stringify(req.body || {});
}

function createSignature(secret: string, timestamp: string, rawBody: string): string {
  const payload = `${timestamp}.${rawBody}`;
  const digest = crypto
    .createHmac("sha256", secret)
    .update(payload, "utf8")
    .digest("hex");

  return `sha256=${digest}`;
}

function timingSafeEqualText(a: string, b: string): boolean {
  const ab = Buffer.from(a, "utf8");
  const bb = Buffer.from(b, "utf8");

  if (ab.length !== bb.length) return false;
  return crypto.timingSafeEqual(ab, bb);
}

function isTimestampValid(timestamp: string): boolean {
  const maxSkewMs = Number(process.env.OXIRANKER_WEBHOOK_MAX_SKEW_MS || 300000);
  const date = new Date(timestamp);

  if (Number.isNaN(date.getTime())) return false;

  const diff = Math.abs(Date.now() - date.getTime());
  return diff <= maxSkewMs;
}

app.post("/api/oxiranker/webhook", async (req, res) => {
  const secret = String(process.env.OXIRANKER_WEBHOOK_SECRET || "").trim();

  if (!secret) {
    return res.status(500).json({
      error: "webhook_secret_missing",
      message: "Webhook secret is not configured.",
    });
  }

  const eventHeader = getHeader(req, "x-oxiranker-event");
  const domainIdHeader = getHeader(req, "x-oxiranker-domain-id");
  const timestamp = getHeader(req, "x-oxiranker-timestamp");
  const receivedSignature = getHeader(req, "x-oxiranker-signature");
  const rawBody = getRawBody(req);

  if (!eventHeader) {
    return res.status(400).json({
      error: "missing_webhook_event",
      message: "Webhook event header is required.",
    });
  }

  if (!domainIdHeader) {
    return res.status(400).json({
      error: "missing_webhook_domain_id",
      message: "Webhook domain id header is required.",
    });
  }

  if (!timestamp) {
    return res.status(400).json({
      error: "missing_webhook_timestamp",
      message: "Webhook timestamp header is required.",
    });
  }

  if (!receivedSignature) {
    return res.status(400).json({
      error: "missing_webhook_signature",
      message: "Webhook signature header is required.",
    });
  }

  if (!isTimestampValid(timestamp)) {
    return res.status(401).json({
      error: "invalid_webhook_timestamp",
      message: "Webhook timestamp is expired or invalid.",
    });
  }

  const expectedSignature = createSignature(secret, timestamp, rawBody);

  if (!timingSafeEqualText(expectedSignature, receivedSignature)) {
    return res.status(401).json({
      error: "invalid_webhook_signature",
      message: "Invalid webhook signature.",
    });
  }

  const payload = req.body || {};
  const event = String(payload.event || eventHeader || "").trim();

  if (event === "webhook.test") {
    const sourceDomainId = Number(payload.domain_id || domainIdHeader);

    return res.status(200).json({
      ok: true,
      message: "Webhook test received successfully.",
      event,
      source_domain_id:
        Number.isFinite(sourceDomainId) && sourceDomainId > 0 ? sourceDomainId : null,
      test: true,
    });
  }

  if (event !== "article.created" && event !== "article.updated") {
    return res.status(400).json({
      error: "unsupported_webhook_event",
      message: "Webhook event is not supported.",
    });
  }

  const sourceDomainId = Number(payload.domain_id || domainIdHeader);
  const sourceArticleId = Number(payload.article_id);

  if (!Number.isFinite(sourceDomainId) || sourceDomainId <= 0) {
    return res.status(400).json({
      error: "invalid_domain_id",
      message: "Webhook domain id is invalid.",
    });
  }

  if (!Number.isFinite(sourceArticleId) || sourceArticleId <= 0) {
    return res.status(400).json({
      error: "invalid_article_id",
      message: "Webhook article id is invalid.",
    });
  }

  // Fetch the full article from Export API and store it in the database here.
  // await syncArticleFromOxiranker(sourceDomainId, sourceArticleId);

  return res.status(200).json({
    ok: true,
    message: "Webhook processed successfully.",
    event,
    source_domain_id: sourceDomainId,
    source_article_id: sourceArticleId,
  });
});
SCHRITT 21

Empfohlene Datenbank für die Zielwebsite

Die Zielwebsite muss Artikel in ihrer eigenen Datenbank speichern und Duplikate mit einer eindeutigen Einschränkung verhindern.

CREATE TABLE public.site_articles (
  id BIGSERIAL PRIMARY KEY,

  source_provider VARCHAR NOT NULL DEFAULT 'oxiranker',
  source_domain_id BIGINT NOT NULL,
  source_article_id BIGINT NOT NULL,
  source_request_id BIGINT NULL,

  lang VARCHAR NOT NULL,
  lang_label VARCHAR NULL,
  dir VARCHAR NOT NULL,
  is_rtl BOOLEAN NULL,

  length VARCHAR NULL,
  topic_text TEXT NULL,

  slug VARCHAR NOT NULL,
  canonical_path VARCHAR NULL,
  effective_canonical_url TEXT NULL,

  title TEXT NOT NULL,
  meta_description TEXT NULL,
  html_body TEXT NULL,

  focus_keywords JSONB NOT NULL DEFAULT '[]'::jsonb,
  category VARCHAR NULL,
  tags JSONB NOT NULL DEFAULT '[]'::jsonb,

  reading_time_minutes INTEGER NULL,

  og_title TEXT NULL,
  og_description TEXT NULL,

  cover_image_url TEXT NULL,
  og_image_url TEXT NULL,

  article_schema JSONB NOT NULL DEFAULT '{}'::jsonb,

  raw_list_item JSONB NOT NULL DEFAULT '{}'::jsonb,
  raw_detail_payload JSONB NOT NULL DEFAULT '{}'::jsonb,

  source_created_at TIMESTAMPTZ NULL,
  source_updated_at TIMESTAMPTZ NULL,

  sync_status VARCHAR NOT NULL DEFAULT 'synced',
  last_synced_at TIMESTAMPTZ NOT NULL DEFAULT now(),
  last_sync_error TEXT NULL,

  publish_status VARCHAR NOT NULL DEFAULT 'draft',
  published_at TIMESTAMPTZ NULL,
  deleted_at TIMESTAMPTZ NULL,

  created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
  updated_at TIMESTAMPTZ NOT NULL DEFAULT now(),

  source_cover_image_url TEXT NULL,
  source_og_image_url TEXT NULL,

  image_mirror_status VARCHAR NOT NULL DEFAULT 'pending',
  image_mirror_error TEXT NULL,
  image_mirrored_at TIMESTAMPTZ NULL,

  CONSTRAINT site_articles_dir_check
    CHECK (dir IN ('ltr', 'rtl')),

  CONSTRAINT site_articles_reading_time_check
    CHECK (reading_time_minutes IS NULL OR reading_time_minutes >= 0),

  CONSTRAINT site_articles_sync_status_check
    CHECK (sync_status IN ('pending', 'syncing', 'synced', 'failed')),

  CONSTRAINT site_articles_publish_status_check
    CHECK (publish_status IN ('draft', 'published', 'archived')),

  CONSTRAINT site_articles_image_mirror_status_check
    CHECK (image_mirror_status IN ('pending', 'not_needed', 'mirrored', 'failed')),

  CONSTRAINT site_articles_source_unique
    UNIQUE (source_provider, source_domain_id, source_article_id)
);
CREATE INDEX site_articles_source_lookup_idx
ON public.site_articles (source_provider, source_domain_id, source_article_id);

CREATE INDEX site_articles_source_updated_at_idx
ON public.site_articles (source_updated_at DESC);

CREATE INDEX site_articles_slug_idx
ON public.site_articles (slug)
WHERE deleted_at IS NULL;

CREATE UNIQUE INDEX site_articles_lang_slug_active_unique
ON public.site_articles (lang, slug)
WHERE deleted_at IS NULL;

CREATE INDEX site_articles_lang_publish_idx
ON public.site_articles (lang, publish_status)
WHERE deleted_at IS NULL;

CREATE INDEX site_articles_published_at_idx
ON public.site_articles (published_at DESC)
WHERE deleted_at IS NULL;

CREATE INDEX site_articles_focus_keywords_gin_idx
ON public.site_articles USING gin (focus_keywords);

CREATE INDEX site_articles_tags_gin_idx
ON public.site_articles USING gin (tags);

CREATE INDEX site_articles_article_schema_gin_idx
ON public.site_articles USING gin (article_schema);
PostgreSQL-Hinweis
In PostgreSQL werden einfache Anführungszeichen für persischen oder Unicode-Text verwendet. Etwas wie N'...' ist nicht erforderlich und darf nicht verwendet werden.
SCHRITT 22

Duplikate mit UPSERT verhindern

Der Artikel darf nicht bei jedem Webhook erneut eingefügt werden. Wenn er bereits existiert, muss er aktualisiert werden.

Hauptschlüssel zur Verhinderung von Duplikaten
Die wichtigste Einschränkung zur Verhinderung von Duplikaten ist: UNIQUE (source_provider, source_domain_id, source_article_id)
INSERT INTO public.site_articles (
  source_provider,
  source_domain_id,
  source_article_id,
  source_request_id,
  lang,
  dir,
  slug,
  title,
  meta_description,
  html_body,
  focus_keywords,
  category,
  tags,
  reading_time_minutes,
  cover_image_url,
  og_image_url,
  article_schema,
  source_created_at,
  source_updated_at,
  sync_status,
  last_synced_at,
  publish_status,
  published_at,
  created_at,
  updated_at
)
VALUES (
  'oxiranker',
  1,
  5,
  5,
  'fa',
  'rtl',
  'smart-internal-links',
  'Article title',
  'Meta description',
  '<p>HTML body</p>',
  '[]'::jsonb,
  'SEO',
  '[]'::jsonb,
  7,
  'https://example.com/uploads/site-articles/oxiranker/1/5/cover.webp',
  'https://example.com/uploads/site-articles/oxiranker/1/5/cover.webp',
  '{}'::jsonb,
  '2026-05-20T19:33:23.618Z',
  '2026-05-20T19:34:09.629Z',
  'synced',
  now(),
  'published',
  now(),
  now(),
  now()
)
ON CONFLICT (source_provider, source_domain_id, source_article_id)
DO UPDATE SET
  source_request_id = EXCLUDED.source_request_id,
  lang = EXCLUDED.lang,
  dir = EXCLUDED.dir,
  slug = EXCLUDED.slug,
  title = EXCLUDED.title,
  meta_description = EXCLUDED.meta_description,
  html_body = EXCLUDED.html_body,
  focus_keywords = EXCLUDED.focus_keywords,
  category = EXCLUDED.category,
  tags = EXCLUDED.tags,
  reading_time_minutes = EXCLUDED.reading_time_minutes,
  cover_image_url = EXCLUDED.cover_image_url,
  og_image_url = EXCLUDED.og_image_url,
  article_schema = EXCLUDED.article_schema,
  source_created_at = EXCLUDED.source_created_at,
  source_updated_at = EXCLUDED.source_updated_at,
  sync_status = 'synced',
  last_synced_at = now(),
  last_sync_error = NULL,
  publish_status = CASE
    WHEN public.site_articles.publish_status = 'archived'
    THEN public.site_articles.publish_status
    ELSE 'published'
  END,
  published_at = COALESCE(public.site_articles.published_at, EXCLUDED.published_at, now()),
  deleted_at = NULL,
  updated_at = now();
SCHRITT 23

Den vollständigen Artikel nach dem Webhook abrufen und synchronisieren

Wenn ein Webhook eintrifft, muss die Zielwebsite den vollständigen Artikel aus Export API abrufen, die Bilder spiegeln und den Artikel per UPSERT speichern.

async function fetchOxirankerArticleDetail(args: {
  baseUrl: string;
  token: string;
  domainId: number;
  articleId: number;
}) {
  const url = `${args.baseUrl.replace(/\/+$/, "")}/api/v1/users/domains/${args.domainId}/articles/${args.articleId}/export/json`;

  const response = await fetch(url, {
    method: "GET",
    headers: {
      Authorization: `Bearer ${args.token}`,
      Accept: "application/json",
      "User-Agent": "My-Site-Oxiranker-Sync/1.0",
    },
  });

  const bodyText = await response.text();

  if (response.status < 200 || response.status > 299) {
    throw new Error(
      `Export API request failed with HTTP status ${response.status}. Response: ${bodyText.slice(0, 1000)}`
    );
  }

  try {
    return JSON.parse(bodyText);
  } catch {
    throw new Error("Export API returned invalid JSON.");
  }
}
async function syncArticleFromWebhook(payload: any) {
  const sourceDomainId = Number(payload.domain_id);
  const sourceArticleId = Number(payload.article_id);

  if (!Number.isFinite(sourceDomainId) || sourceDomainId <= 0) {
    throw new Error("Webhook domain id is invalid.");
  }

  if (!Number.isFinite(sourceArticleId) || sourceArticleId <= 0) {
    throw new Error("Webhook article id is invalid.");
  }

  const article = await fetchOxirankerArticleDetail({
    baseUrl: process.env.OXIRANKER_EXPORT_API_BASE_URL!,
    token: process.env.OXIRANKER_EXPORT_API_TOKEN!,
    domainId: sourceDomainId,
    articleId: sourceArticleId,
  });

  const mirroredImages = await mirrorArticleImages(article);

  const finalHtmlBody = replaceImageUrls(article.html_body, mirroredImages);
  const finalArticleSchema = replaceImageUrlsInJson(article.article_schema, mirroredImages);

  await upsertSiteArticle({
    source_provider: process.env.OXIRANKER_SOURCE_PROVIDER || "oxiranker",
    source_domain_id: sourceDomainId,
    source_article_id: sourceArticleId,
    source_request_id: article.request_id,
    lang: article.lang,
    dir: article.dir,
    slug: article.slug,
    title: article.title,
    meta_description: article.meta_description,
    html_body: finalHtmlBody,
    focus_keywords: article.focus_keywords,
    category: article.category,
    tags: article.tags,
    reading_time_minutes: article.reading_time_minutes,
    cover_image_url: mirroredImages.cover_image_url,
    og_image_url: mirroredImages.og_image_url,
    article_schema: finalArticleSchema,
    raw_detail_payload: article,
    source_created_at: article.created_at,
    source_updated_at: article.updated_at,
  });
}
SCHRITT 24

Image Mirroring und Regeln zur Bildspeicherung

Die Zielwebsite muss Bilder herunterladen, auf ihrer eigenen Infrastruktur speichern und Artikel-URLs ersetzen.

Regeln für Image Mirroring

Es dürfen nur HTTPS-URLs akzeptiert werden.
Die Datei muss tatsächlich ein Bild sein.
content-type muss mit image/ beginnen.
Leere Dateien dürfen nicht gespeichert werden.
Die Dateigröße darf das Limit nicht überschreiten.
Wenn cover und og identisch sind, nur einmal herunterladen.
Nach dem Spiegeln URLs in html_body und article_schema ersetzen.

Empfohlener Speicherpfad

/uploads/site-articles/oxiranker/1/5/cover.webp
/uploads/site-articles/oxiranker/1/5/og.webp
https://example.com/uploads/site-articles/oxiranker/1/5/cover.webp
SCHRITT 25

Tägliche Synchronisierung für Zuverlässigkeit

Webhook allein reicht nicht aus. Die Zielwebsite sollte zusätzlich eine tägliche Synchronisierung haben, um verpasste Artikel wiederherzustellen.

Warum ist tägliche Synchronisierung nötig?

Die Zielwebsite kann vorübergehend nicht erreichbar sein.
Webhook kann einen Timeout haben.
Deployment- oder Netzwerkprobleme können auftreten.
Ein Artikel kann generiert werden, aber sein Webhook kann verpasst werden.

Algorithmus der täglichen Synchronisierung

Offset bei 0 starten.
limit 20 oder 50 verwenden.
Die Artikelliste aus der Export List API abrufen.
Für jedes Element das Export JSON dieses Artikels abrufen.
Die Bilder spiegeln.
Den Artikel mit UPSERT speichern.
Offset erhöhen und fortfahren, bis total abgeschlossen ist.
SCHRITT 26

Artikel auf der Zielwebsite anzeigen

Nachdem Artikel in site_articles gespeichert wurden, darf die Zielwebsite nicht bei jeder öffentlichen Anzeige eine Verbindung zu OxiRanker herstellen. Sie muss aus ihrer eigenen Datenbank lesen.

Blog-Listenseite

SELECT
  id,
  lang,
  lang_label,
  dir,
  is_rtl,
  slug,
  title,
  meta_description,
  category,
  tags,
  reading_time_minutes,
  cover_image_url,
  og_image_url,
  source_created_at,
  published_at,
  created_at
FROM public.site_articles
WHERE source_provider = 'oxiranker'
  AND deleted_at IS NULL
  AND sync_status = 'synced'
  AND publish_status = 'published'
ORDER BY COALESCE(source_created_at, published_at, created_at) DESC, id DESC
LIMIT 20 OFFSET 0;

Artikeldetailseite

SELECT *
FROM public.site_articles
WHERE source_provider = 'oxiranker'
  AND lang = 'fa'
  AND slug = 'smart-internal-links'
  AND deleted_at IS NULL
  AND sync_status = 'synced'
  AND publish_status = 'published'
LIMIT 1;
Empfohlene Artikel-URL
Für mehrsprachige Websites kannst du das Muster /{lang}/blog/{slug} verwenden. Beispiel: /fa/blog/smart-internal-links
SCHRITT 27

SEO, Schema, RTL und HTML-Rendering

Die Zielwebsite muss SEO-Daten aus ihrer eigenen Datenbank lesen und auf der Artikelseite rendern.

SEO-Felder

title für den Seitentitel
meta_description für die Meta-Beschreibung
effective_canonical_url für canonical
og_title und og_description für Open Graph
og_image_url oder cover_image_url für das Social-Preview-Bild
article_schema für JSON-LD

HTML-Rendering

<div
  className="article-content"
  dangerouslySetInnerHTML={{ __html: article.html_body }}
/>

Wenn die Zielwebsite eine strikte Sicherheitsrichtlinie hat, kann sie HTML vor dem Speichern oder vor der Anzeige bereinigen.

RTL und LTR

Jeder Artikel hat die Felder lang, dir und is_rtl. Wenn dir rtl ist, muss die Artikelseite von rechts nach links gerendert werden.

<article dir="rtl">
  ...
</article>
SCHRITT 28

Häufige Fehler und ihre Bedeutung

Fehler der Zielwebsite müssen auf Englisch, klar und verständlich sein, damit Fehlerbehebung schnell durchgeführt werden kann.

Export-API-Fehler

Domain-Export-Token fehlt.
Der Export-API-Token wurde nicht gesendet. Authorization: Bearer YOUR_EXPORT_TOKEN muss gesendet werden.
Ungültiger Domain-Export-Token.
Der Token ist falsch, wurde rotiert, gehört nicht zu dieser Domain oder die domainId ist falsch.
Der Token hat nicht den erforderlichen Scope.
Der Token hat nicht den erforderlichen Scope. Neue Token müssen articles:read und exports:read enthalten.
Ungültiger lang-Wert.
Der lang-Wert im Query-Parameter wurde falsch gesendet.

Webhook-Fehler

Webhook-URL muss HTTPS verwenden.
Webhook-URL muss mit https:// beginnen.
Ungültige Webhook-Signatur.
Webhook Secret ist falsch, das Secret wurde rotiert, der raw body wurde geändert oder die Signatur wurde falsch berechnet.
Webhook-Timestamp ist abgelaufen oder ungültig.
Der Timestamp ist alt, liegt in der Zukunft oder ist ungültig. Prüfe die Serveruhr und NTP.
Webhook-Testzustellung fehlgeschlagen.
Die Zielwebsite hat keinen erfolgreichen Status von 200 bis 299 zurückgegeben, hatte einen Timeout oder hat webhook.test nicht korrekt verarbeitet.
SCHRITT 29

Abschließende API- und Webhook-Test-Checkliste

Nach der Implementierung prüfen diese Tests die Verbindung von Anfang bis Ende.

Export-Token- und JSON-Test

GET https://oxiranker.com/api/v1/users/domains/1/export/articles?limit=2&offset=0
Authorization: Bearer YOUR_EXPORT_TOKEN
Accept: application/json
GET https://oxiranker.com/api/v1/users/domains/1/articles/5/export/json
Authorization: Bearer YOUR_EXPORT_TOKEN
Accept: application/json

Webhook-Test

Wenn du den Ziel-Webhook mit Postman ohne Signatur aufrufst, musst du einen Sicherheitsfehler erhalten. Das bedeutet, dass der Endpunkt aktiv ist und Sicherheit prüft.

{
  "error": "missing_webhook_signature",
  "message": "Webhook signature header is required."
}

Klicke anschließend in OxiRanker auf Test Webhook. Wenn der Empfänger webhook.test unterstützt, muss response_status 200 sein.

Nach der Rotation
Wenn Webhook Secret oder Export Token rotiert wird, kopiere den neuen Wert sofort, ersetze ihn in der .env der Zielwebsite und starte das Ziel-Backend neu oder stelle es erneut bereit.
SCHRITT 30

Wichtige Regeln für die korrekte Implementierung

Diese Regeln müssen bei der API-Integration befolgt werden, damit das System sicher, stabil und wartbar bleibt.

Export Token darf nur im Backend gespeichert werden.
Webhook Secret darf nur im Backend gespeichert werden.
Der Ziel-Webhook-Endpunkt muss exakt /api/oxiranker/webhook sein.
Die Webhook-URL muss HTTPS verwenden.
Webhook-Signatur und Timestamp müssen immer verifiziert werden.
Artikel müssen mit UPSERT gespeichert werden, nicht mit einem einfachen Insert.
Eine eindeutige Einschränkung ist erforderlich, um Duplikate zu verhindern.
Bilder müssen von OxiRanker heruntergeladen und auf der Zielwebsite gespeichert werden.
Die Zielwebsite darf nicht dauerhaft von OxiRanker-Bild-URLs abhängig sein.
Bild-URLs müssen in html_body und article_schema ersetzt werden.
Wenn Webhook fehlschlägt, muss die tägliche Synchronisierung den verpassten Artikel wiederherstellen.
Fehler, die Nutzern oder Logs angezeigt werden, müssen auf Englisch und verständlich sein.
Wenn ein Artikel archiviert ist, darf die automatische Synchronisierung ihn nicht erneut veröffentlichen.
raw_detail_payload und raw_list_item müssen bewahrt werden, damit keine ursprünglichen Ausgabedaten verloren gehen.
Wenn Export Token oder Webhook Secret rotiert wird, muss der neue Wert in .env ersetzt und das Backend neu gestartet oder neu bereitgestellt werden.
SCHRITT 31

Zusammenfassung des vollständigen API-Ablaufs

Nach Abschluss dieser Schritte kann deine individuelle Website von OxiRanker generierte Artikel sicher empfangen, speichern, spiegeln und veröffentlichen.

Bei OxiRanker registrieren
Eine Domain hinzufügen
Domaininhaberschaft verifizieren
Sprachen auswählen und Blog-URL hinzufügen
Keywords für jede Sprache hinzufügen
Article Profile vervollständigen
Telegram bei Bedarf konfigurieren
Automatische Artikel-Engine konfigurieren
Den ersten Artikel über Generate erstellen
Preview und SEO Report prüfen
Export Token aus API Output erhalten
Einsatzbereite Export-API-Endpunkte erhalten
Webhook Secret erhalten
Den festen Endpunkt /api/oxiranker/webhook im Ziel-Backend erstellen
Token und Secret in der .env der Zielwebsite speichern
Die Tabelle site_articles und eine eindeutige Einschränkung erstellen
Abrufen des vollständigen Artikels aus Export JSON implementieren
Image Mirroring implementieren
UPSERT implementieren
Tägliche Synchronisierung implementieren
Artikel, SEO, OG und Schema aus der Zieldatenbank rendern
Abschließende Export-API- und Webhook-Tests ausführen