Documentazione OxiRanker
Un centro di documentazione chiaro per collegare il tuo sito web ai servizi OxiRanker. Inizia con l’integrazione WordPress o con l’integrazione API, poi segui la guida passo dopo passo.
Servizio API
Usa Export API e Webhook per siti personalizzati, piattaforme CMS personalizzate, Next.js, Laravel, Node.js, PHP o backend Python.
OxiRanker non è un hosting permanente per il sito di destinazione
OxiRanker non fornisce una garanzia di hosting permanente per i contenuti, le immagini o i file esportati del sito di destinazione. OxiRanker non è un servizio di hosting per contenuti degli utenti e i contenuti generati, le immagini di copertina, le immagini Open Graph o i file di output potrebbero non rimanere sull’infrastruttura di OxiRanker per più di 90 giorni.
Pertanto, dopo aver ricevuto l’output, il sito di destinazione deve salvare l’articolo finale, le immagini, i dati SEO, i dati Open Graph e i dati Schema nel proprio database, server, storage o CDN, e non deve dipendere dagli URL di OxiRanker per la visualizzazione pubblica.
Video formativo sulla connessione
Usa questo video come guida visiva prima di collegare WordPress o implementare l’integrazione API.
Implementa il flusso di output passo dopo passo
Salva prima Export Token e Webhook Secret nel tuo backend, poi completa Webhook receiver, database, logica UPSERT, Image Mirroring e sync giornaliero.
Registrarsi e creare un dominio in OxiRanker
Per prima cosa, registrati in OxiRanker, accedi al tuo account e clicca su Add Domain dalla sezione Domains.
Formato corretto del dominio
Nel campo Domain name, inserisci solo il dominio root. L’indirizzo non deve includere http, https o www.
example.comFormati errati
Non inserire questi formati nel campo del dominio.
https://example.com
http://example.com
www.example.comVerificare la proprietà del dominio
In questo passaggio devi dimostrare che il dominio appartiene a te o che hai accesso per gestirlo.
DNS TXT
ConsigliatoAggiungi un TXT record al DNS del dominio. Per Cloudflare, questo è di solito il metodo più semplice.
DNS Name:
_yourdomain-verification.example.com
TXT Value:
acb1****29803HTTP File
Inserisci un file di testo nel percorso .well-known del tuo sito web in modo che OxiRanker possa verificarlo.
File URL:
https://example.com/.well-known/yourdomain-verification.txt
File Content:
220daaa37****42730f27Meta Tag
Inserisci un meta tag nella sezione head della homepage del tuo sito web.
<meta name="yourdomain-verification" content="190208d****d275b65" />Ricevere token gratuiti dopo la verifica del primo dominio
Dopo aver verificato il primo dominio, OxiRanker aggiunge 1000 token gratuiti al tuo account per i test iniziali.
Configurare lingue e Blog URL
Nella sezione Blog URLs, definisci se il tuo sito web è monolingua o multilingua e quali lingue devono essere usate per gli articoli generati.
Scegliere le lingue
OxiRanker supporta 24 lingue. Puoi scegliere una o più lingue in base alla struttura del tuo sito web.
Inserire Blog URL
Per ogni lingua, inserisci il blog URL di quella lingua. Se il tuo sito è multilingua, inserisci separatamente il percorso di ogni lingua.
https://example.com/fa/blog
https://example.com/en/blogAggiungere keyword per ogni lingua
Nel tab Keywords, aggiungi almeno 3 e fino a 50 keyword per ogni lingua.
Regola principale
Le keyword di ogni lingua devono essere inserite nella stessa lingua. Se la lingua dell’articolo è inglese, anche le keyword devono essere in inglese.
Come aggiungere keyword
Dopo aver digitato ogni keyword, premi Enter per aggiungerla. Dopo aver completato tutte le keyword, clicca su Save.
Completare il Domain Article Profile
Article Profile indica a OxiRanker cos’è il tuo brand, per chi scrive, quale tono deve usare e quali argomenti deve evitare.
Settore
Definisce il mercato o la categoria aziendale. Esempio: consulenza immobiliare e compravendita di immobili a Dubai.
Descrizione dell’attività
Spiega cosa vende l’azienda, chi serve, come funziona e cosa la rende diversa.
Pubblico
Definisce a chi deve rivolgersi il contenuto. Ogni pubblico può essere salvato separatamente.
Tono
Controlla lo stile e la voce del contenuto generato, come formale, educativo, semplice, tecnico o orientato alla vendita.
Obiettivo del contenuto
Definisce cosa devono ottenere gli articoli, come attirare clienti, aumentare il traffico o educare gli utenti.
Argomenti vietati
Qualsiasi cosa di cui il brand non dovrebbe parlare o a cui non dovrebbe essere associato viene inserita qui.
CTA opzionale
CTA significa l’azione che vuoi che il lettore compia dopo aver letto l’articolo. Se non sei sicuro di quale testo sia adatto, è meglio lasciare vuoto questo campo.
Link interni ed esterni
OxiRanker può applicare in modo intelligente link interni ed esterni all’interno degli articoli.
Link interni
Questa opzione è attiva di default ed è meglio mantenerla attiva. I link interni aiutano i motori di ricerca a comprendere meglio la struttura dei contenuti del tuo sito.
Nel primo articolo, di solito i link interni non vengono creati perché non esiste ancora un articolo precedente. Dal secondo articolo in poi, il sistema può eseguire il linking interno.
Link esterni
I link esterni vengono aggiunti a fonti ufficiali e affidabili e sono contrassegnati come nofollow, così l’autorità SEO non viene trasferita involontariamente.
Controllare la qualità del profilo con AI
Dopo aver completato l’Article Profile, OxiRanker revisiona la qualità del profilo.
Collegare Telegram per i riepiloghi degli articoli
Dopo aver salvato correttamente il profilo, puoi abilitare l’invio dei riepiloghi degli articoli a un canale Telegram.
Aggiungere il bot
Aggiungi @Oxiranker_Bot al tuo canale Telegram e rendilo Admin.
Inserire lo username del canale
Inserisci il nome del canale con @. Esempio: @my_channel
Abilitare l’invio del riepilogo
Dopo aver registrato un canale valido, abilita l’invio del riepilogo degli articoli.
Configurare il motore automatico degli articoli
Nel tab Automatic article generation, puoi gestire il motore di generazione automatica degli articoli.
Selezione intelligente dell’argomento
Il sistema usa keyword e Article Profile per selezionare argomenti adatti, unici e pubblicabili.
Generazione consapevole del brand
Tono, pubblico, obiettivo del contenuto e restrizioni del brand vengono rispettati durante la generazione dell’articolo.
Pubblicazione programmata
La capacità mensile viene distribuita durante il mese, così la pubblicazione dei contenuti appare più naturale e costante.
Capacità mensile
7 articles / month
10 articles / month
15 articles / month
30 articles / month30 articoli significa quasi un articolo al giorno. 15 articoli significa quasi un giorno sì e uno no. 10 articoli significa circa ogni due o tre giorni. 7 articoli significa circa ogni tre o quattro giorni.
Lunghezza dell’articolo e immagine
La lunghezza dell’articolo può essere Short, Standard o Long. Standard è una scelta equilibrata per la maggior parte dei siti.
Le immagini degli articoli possono essere abilitate o disabilitate, e il tema dell’immagine può essere Light o Dark.
Creare il primo articolo da Generate
Dopo aver preparato il dominio, apri il menu Generate, scegli il dominio e clicca su New Content o New Article.
Lingua
Per ogni richiesta di contenuto viene selezionata una sola lingua.
Argomento
Scrivi un argomento chiaro. Source URL è opzionale.
Immagine
Abilita o disabilita l’immagine e scegli il tema Light o Dark.
Pagamento e generazione
Il costo viene detratto dal wallet e la generazione dell’articolo inizia.
Revisionare Preview, SEO Report e JSON Output
Dopo la generazione dell’articolo, sono disponibili diversi output importanti per la revisione della qualità e la preparazione alla pubblicazione.
Preview
Mostra il testo dell’articolo e l’immagine generata. Questa sezione è utile per revisionare la qualità finale prima della pubblicazione.
SEO Report
Mostra un report SEO comprensibile che include SEO Score, title, meta description, canonical, keywords, schema, headings, links, images e OpenGraph.
JSON Output
Mostra l’output completo dell’articolo per utenti tecnici, inclusi slug, html_body, schema, URL immagini, tags, category, tempo di lettura e timestamp di creazione/aggiornamento.
Cosa fanno esattamente Export API e Webhook?
Dopo che un articolo viene generato in OxiRanker, il sito di destinazione può leggerlo con Export API o ricevere una notifica tramite Webhook quando l’articolo è pronto.
Metodo 1: Export API
Il sito di destinazione può leggere gli articoli da OxiRanker con Export Token quando necessario. Questo metodo viene usato per sync manuale, sync giornaliero, pagine elenco articoli e recupero del JSON completo dell’articolo.
GET https://oxiranker.com/api/v1/users/domains/1/export/articles?limit=20&offset=0
Authorization: Bearer YOUR_EXPORT_TOKEN
Accept: application/jsonGET https://oxiranker.com/api/v1/users/domains/1/articles/5/export/json
Authorization: Bearer YOUR_EXPORT_TOKEN
Accept: application/jsonMetodo 2: Webhook
Quando viene creato un nuovo articolo o un articolo viene aggiornato, OxiRanker notifica l’endpoint del sito di destinazione. Webhook non invia l’articolo completo. Invia solo l’identificatore dell’articolo, così il sito di destinazione può recuperare l’articolo completo dalla Export JSON API.
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"
}Responsabilità di archiviazione dei contenuti e Image Mirroring
Il sito di destinazione deve salvare l’articolo finale e le immagini sulla propria infrastruttura.
Cosa deve salvare il sito di destinazione
Esempio corretto di percorso immagine
Usare gli URL delle immagini OxiRanker direttamente e in modo permanente non è corretto. L’immagine deve essere scaricata e gli URL dell’articolo devono essere sostituiti con gli URL interni del sito di destinazione.
https://oxiranker.com/uploads/articles/3_5/cover.webphttps://example.com/uploads/site-articles/oxiranker/1/5/cover.webpOttenere Export Token ed endpoint pronti
Export Token ed endpoint pronti all’uso sono disponibili dalla pagina dell’articolo, dal tab Outputs e dalla sezione API Output.
Dove ottenere Export Token
https://oxiranker.com/en/pannel/generate/{domainId}/articles/{articleId}Endpoint pronti all’uso
Nella stessa pagina dell’articolo, dentro la sezione API Output, Ready-to-use endpoints mostra URL preparati.
https://oxiranker.com/api/v1/users/domains/1/export/articles?limit=20&offset=0https://oxiranker.com/api/v1/users/domains/1/articles/1/export/jsonhttps://oxiranker.com/api/v1/users/domains/1/articles/1/export/html?canonical_mode=absolute&include_css=0Creare Webhook Secret ed endpoint di destinazione
Webhook Secret viene creato anche dalla pagina dell’articolo, dal tab Outputs e dalla sezione API Output.
Dove viene creato Webhook Secret?
Endpoint fisso di destinazione
OxiRanker accetta solo il seguente percorso fisso per il Webhook di destinazione. Il sito di destinazione deve creare questo endpoint nel proprio backend.
POST /api/oxiranker/webhook
https://example.com/api/oxiranker/webhookQuesto endpoint deve accettare solo POST, preservare il raw body, verificare timestamp e signature, e restituire una risposta corretta per webhook.test.
Valori .env richiesti per il sito di destinazione
OxiRanker non ha accesso ai file del sito di destinazione o al suo .env. Lo sviluppatore del sito di destinazione deve salvare questi valori nel proprio backend.
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=50Valori di sicurezza
Valori di archiviazione
Export API: List, JSON, HTML e ZIP
Il sito di destinazione può usare Export API per ricevere l’elenco degli articoli o l’output completo di ogni articolo.
Ottenere l’elenco degli articoli per un dominio
GET https://oxiranker.com/api/v1/users/domains/:domainId/export/articles?limit=20&offset=0
Authorization: Bearer YOUR_EXPORT_TOKEN
Accept: application/jsonlimit definisce il numero di articoli ed è attualmente limitato tra 1 e 50. offset viene usato per la paginazione. lang è opzionale e viene usato per recuperare articoli di una lingua specifica come fa, en o tr.
Ottenere l’articolo completo come JSON
GET https://oxiranker.com/api/v1/users/domains/:domainId/articles/:articleId/export/json
Authorization: Bearer YOUR_EXPORT_TOKEN
Accept: application/jsonQuesto è l’output più importante per il sito di destinazione. Il sito di destinazione deve salvare il JSON completo dell’articolo nel proprio database e leggere dal proprio database per la visualizzazione pubblica.
Ottenere HTML pronto
GET https://oxiranker.com/api/v1/users/domains/1/articles/5/export/html?canonical_mode=absolute&include_css=0
Authorization: Bearer YOUR_EXPORT_TOKENHTML è adatto ai siti che vogliono un output pronto, ma l’approccio più professionale è salvare JSON e renderizzarlo con il template del sito di destinazione.
Ottenere output ZIP
GET https://oxiranker.com/api/v1/users/domains/1/articles/5/export/zip
Authorization: Bearer YOUR_EXPORT_TOKENL’output ZIP include article.json, meta.json, assets.json, schema.jsonld, index.html, version.txt e README.txt.
Firma Webhook e ricezione sicura del Webhook
Il sito di destinazione deve verificare la firma del Webhook usando il raw body originale.
Headers Webhook
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=...Formula della firma
payload_to_sign = timestamp + "." + rawBody
signature = "sha256=" + HMAC_SHA256(webhook_secret, payload_to_sign)Se il sito di destinazione esegue prima il parse del JSON e poi lo stringifica di nuovo, ordine o spaziatura possono cambiare e la firma può diventare non valida. Per verificare la firma deve essere usato il raw body originale.
Semplice esempio di codice Node.js per ricevere Webhook
Questo esempio Express gestisce raw body, timestamp, signature, webhook.test ed eventi articolo.
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,
});
});Database consigliato per il sito di destinazione
Il sito di destinazione deve salvare gli articoli nel proprio database e prevenire i duplicati con una unique constraint.
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);Prevenire duplicati con UPSERT
L’articolo non deve essere inserito di nuovo per ogni Webhook. Se esiste già, deve essere aggiornato.
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();Recuperare l’articolo completo e sincronizzare dopo Webhook
Quando arriva un Webhook, il sito di destinazione deve recuperare l’articolo completo da Export API, mirrorare le immagini ed eseguire UPSERT dell’articolo.
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,
});
}Image Mirroring e regole di archiviazione delle immagini
Il sito di destinazione deve scaricare le immagini, salvarle sulla propria infrastruttura e sostituire gli URL dell’articolo.
Regole di Image Mirroring
Percorso di archiviazione consigliato
/uploads/site-articles/oxiranker/1/5/cover.webp
/uploads/site-articles/oxiranker/1/5/og.webphttps://example.com/uploads/site-articles/oxiranker/1/5/cover.webpSync giornaliero per affidabilità
Webhook da solo non è sufficiente. Il sito di destinazione dovrebbe avere anche un sync giornaliero per recuperare gli articoli mancati.
Perché serve il sync giornaliero?
Algoritmo del sync giornaliero
Mostrare gli articoli sul sito di destinazione
Dopo aver salvato gli articoli in site_articles, il sito di destinazione non deve collegarsi a OxiRanker per ogni visualizzazione pubblica. Deve leggere dal proprio database.
Pagina elenco blog
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;Pagina dettaglio articolo
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;SEO, Schema, RTL e rendering HTML
Il sito di destinazione deve leggere i dati SEO dal proprio database e renderizzarli nella pagina articolo.
Campi SEO
Rendering HTML
<div
className="article-content"
dangerouslySetInnerHTML={{ __html: article.html_body }}
/>Se il sito di destinazione ha una policy di sicurezza rigida, può sanificare l’HTML prima del salvataggio o prima della visualizzazione.
RTL e LTR
Ogni articolo ha i campi lang, dir e is_rtl. Se dir è rtl, la pagina articolo deve essere renderizzata da destra a sinistra.
<article dir="rtl">
...
</article>Errori comuni e loro significati
Gli errori del sito di destinazione devono essere in inglese, chiari e comprensibili, così il troubleshooting può essere fatto rapidamente.
Errori Export API
Errori Webhook
Checklist finale di test API e Webhook
Dopo l’implementazione, questi test verificano la connessione dall’inizio alla fine.
Test Export Token e JSON
GET https://oxiranker.com/api/v1/users/domains/1/export/articles?limit=2&offset=0
Authorization: Bearer YOUR_EXPORT_TOKEN
Accept: application/jsonGET https://oxiranker.com/api/v1/users/domains/1/articles/5/export/json
Authorization: Bearer YOUR_EXPORT_TOKEN
Accept: application/jsonTest Webhook
Se chiami il Webhook di destinazione con Postman senza firma, devi ricevere un errore di sicurezza. Questo significa che l’endpoint è attivo e controlla la sicurezza.
{
"error": "missing_webhook_signature",
"message": "Webhook signature header is required."
}Poi clicca su Test Webhook dentro OxiRanker. Se il receiver supporta webhook.test, response_status deve essere 200.
Regole importanti per una corretta implementazione
Queste regole devono essere seguite nell’integrazione API affinché il sistema resti sicuro, stabile e manutenibile.
Riepilogo completo del flusso API
Dopo aver completato questi passaggi, il tuo sito personalizzato può ricevere, salvare, mirrorare e pubblicare in modo sicuro gli articoli generati in OxiRanker.