OxiRanker Sənədləri
Saytınızı OxiRanker xidmətlərinə qoşmaq üçün səliqəli sənədləşmə mərkəzi. WordPress inteqrasiyası və ya API inteqrasiyası ilə başlayın, sonra bələdçini addım-addım izləyin.
API xidməti
Xüsusi saytlar, xüsusi CMS platformaları, Next.js, Laravel, Node.js, PHP və ya Python backend-ləri üçün Export API və Webhook istifadə edin.
OxiRanker təyinat saytı üçün daimi hosting deyil
OxiRanker təyinat saytının məzmunu, şəkilləri və ya ixrac edilmiş faylları üçün daimi hosting zəmanəti vermir. OxiRanker istifadəçi məzmunu üçün hosting xidməti deyil və yaradılmış məzmun, cover şəkilləri, Open Graph şəkilləri və ya çıxış faylları OxiRanker infrastrukturunda 90 gündən artıq qalmaya bilər.
Buna görə, çıxışı qəbul etdikdən sonra təyinat saytı yekun məqaləni, şəkilləri, SEO məlumatlarını, Open Graph məlumatlarını və Schema məlumatlarını öz verilənlər bazasında, serverində, saxlamasında və ya CDN-də saxlamalıdır və açıq göstərim üçün OxiRanker URL-lərinə güvənməməlidir.
Bağlantı təlim videosu
WordPress-i qoşmazdan və ya API inteqrasiyasını tətbiq etməzdən əvvəl bu videodan vizual bələdçi kimi istifadə edin.
Çıxış axınını addım-addım tətbiq edin
Əvvəlcə Export Token və Webhook Secret-i backend-də saxlayın, sonra Webhook receiver, verilənlər bazası, UPSERT məntiqi, Image Mirroring və gündəlik sync-i tamamlayın.
OxiRanker-də qeydiyyatdan keçin və domen yaradın
Əvvəlcə OxiRanker-də qeydiyyatdan keçin, hesabınıza daxil olun və Domains bölməsindən Add Domain düyməsinə klikləyin.
Düzgün domen formatı
Domain name sahəsinə yalnız kök domeni daxil edin. Ünvan http, https və ya www daxil etməməlidir.
example.comYanlış formatlar
Domen sahəsinə bu formatları daxil etməyin.
https://example.com
http://example.com
www.example.comDomen sahibliyini təsdiqləyin
Bu addımda domenin sizə məxsus olduğunu və ya onu idarə etmək üçün girişiniz olduğunu sübut etməlisiniz.
DNS TXT
Tövsiyə olunurDomen DNS-ə TXT record əlavə edin. Cloudflare üçün bu adətən ən sadə üsuldur.
DNS Name:
_yourdomain-verification.example.com
TXT Value:
acb1****29803HTTP File
OxiRanker-in yoxlaya bilməsi üçün saytınızın .well-known yoluna mətn faylı yerləşdirin.
File URL:
https://example.com/.well-known/yourdomain-verification.txt
File Content:
220daaa37****42730f27Meta Tag
Saytınızın ana səhifəsinin head bölməsinə meta tag yerləşdirin.
<meta name="yourdomain-verification" content="190208d****d275b65" />İlk domeni təsdiqlədikdən sonra pulsuz tokenlər alın
İlk domeni təsdiqlədikdən sonra OxiRanker ilkin test üçün hesabınıza 1000 pulsuz token əlavə edir.
Dilləri və Blog URL-ni konfiqurasiya edin
Blog URLs bölməsində saytınızın təkdilli və ya çoxdilli olduğunu və yaradılmış məqalələr üçün hansı dillərin istifadə olunacağını müəyyən edin.
Dilləri seçin
OxiRanker 24 dili dəstəkləyir. Sayt quruluşunuza əsasən bir və ya bir neçə dil seçə bilərsiniz.
Blog URL daxil edin
Hər dil üçün həmin dilə aid blog URL-ni daxil edin. Saytınız çoxdillidirsə, hər dil yolunu ayrıca daxil edin.
https://example.com/fa/blog
https://example.com/en/blogHər dil üçün açar sözlər əlavə edin
Keywords tabında hər dil üçün ən azı 3 və ən çox 50 açar söz əlavə edin.
Əsas qayda
Hər dil üçün açar sözlər eyni dildə daxil edilməlidir. Məqalə dili ingiliscədirsə, açar sözlər də ingiliscə olmalıdır.
Açar sözləri necə əlavə etmək olar
Hər açar sözü yazdıqdan sonra əlavə etmək üçün Enter düyməsinə basın. Bütün açar sözləri tamamladıqdan sonra Save düyməsinə klikləyin.
Domen Article Profile-ni tamamlayın
Article Profile OxiRanker-ə brendinizin nə olduğunu, kimlər üçün yazdığını, hansı tondan istifadə etməli olduğunu və hansı mövzulardan qaçmalı olduğunu bildirir.
Sahə
Bazar və ya biznes kateqoriyasını müəyyən edir. Misal: Dubayda daşınmaz əmlak məsləhətçiliyi və əmlak alqı-satqısı.
Biznes təsviri
Biznesin nə satdığını, kimlərə xidmət etdiyini, necə işlədiyini və onu fərqləndirən cəhətləri izah edir.
Auditoriya
Məzmunun kimə xitab etməli olduğunu müəyyən edir. Hər auditoriya ayrıca saxlanıla bilər.
Ton
Yaradılan məzmunun üslubunu və səsini idarə edir, məsələn rəsmi, tədris yönümlü, sadə, texniki və ya satış yönümlü.
Məzmun məqsədi
Məqalələrin nəyi əldə etməli olduğunu müəyyən edir, məsələn müştəri cəlb etmək, trafiki artırmaq və ya istifadəçiləri maarifləndirmək.
Qadağan olunmuş mövzular
Brendin danışmamalı olduğu və ya əlaqələndirilməməli olduğu hər şey burada daxil edilir.
İstəyə bağlı CTA
CTA oxucunun məqaləni oxuduqdan sonra etməsini istədiyiniz hərəkət deməkdir. Hansı mətnin uyğun olduğuna əmin deyilsinizsə, bu sahəni boş buraxmaq daha yaxşıdır.
Daxili və xarici linkləmə
OxiRanker məqalələr daxilində daxili və xarici linkləri ağıllı şəkildə tətbiq edə bilər.
Daxili linklər
Bu seçim standart olaraq aktivdir və onu aktiv saxlamaq daha yaxşıdır. Daxili linkləmə axtarış motorlarına saytınızın məzmun strukturunu daha yaxşı anlamağa kömək edir.
İlk məqalədə adətən daxili linklər yaradılmır, çünki hələ əvvəlki məqalə yoxdur. İkinci məqalədən etibarən sistem daxili linkləmə edə bilər.
Xarici linklər
Xarici linklər rəsmi və etibarlı mənbələrə əlavə olunur və SEO səlahiyyətinin istəmədən ötürülməməsi üçün nofollow kimi işarələnir.
AI ilə profil keyfiyyətini yoxlayın
Article Profile tamamlandıqdan sonra OxiRanker profil keyfiyyətini yoxlayır.
Məqalə xülasələri üçün Telegram-ı qoşun
Profili uğurla saxladıqdan sonra məqalə xülasələrinin Telegram kanalına göndərilməsini aktivləşdirə bilərsiniz.
Botu əlavə edin
@Oxiranker_Bot-u Telegram kanalınıza əlavə edin və onu Admin edin.
Kanal istifadəçi adını daxil edin
Kanal adını @ ilə daxil edin. Misal: @my_channel
Xülasə göndərilməsini aktivləşdirin
Etibarlı kanal qeydiyyata alındıqdan sonra məqalə xülasəsinin göndərilməsini aktivləşdirin.
Avtomatik məqalə mühərrikini konfiqurasiya edin
Automatic article generation tabında avtomatik məqalə yaratma mühərrikini idarə edə bilərsiniz.
Ağıllı mövzu seçimi
Sistem uyğun, unikal və yayımlana bilən mövzuları seçmək üçün açar sözlərdən və Article Profile-dən istifadə edir.
Brendə uyğun yaradılma
Məqalə yaradılarkən ton, auditoriya, məzmun məqsədi və brend məhdudiyyətləri nəzərə alınır.
Planlaşdırılmış yayımlama
Aylıq tutum ay boyunca paylanır ki, məzmun yayımlanması daha təbii və ardıcıl görünsün.
Aylıq tutum
7 articles / month
10 articles / month
15 articles / month
30 articles / month30 məqalə demək olar ki, gündə bir məqalə deməkdir. 15 məqalə təxminən gün aşırı deməkdir. 10 məqalə təxminən hər iki-üç gündən bir deməkdir. 7 məqalə təxminən hər üç-dörd gündən bir deməkdir.
Məqalə uzunluğu və şəkil
Məqalə uzunluğu Short, Standard və ya Long ola bilər. Standard əksər saytlar üçün balanslı seçimdir.
Məqalə şəkilləri aktiv və ya deaktiv edilə bilər və şəkil mövzusu Light və ya Dark seçilə bilər.
Generate-dən ilk məqaləni yaradın
Domeni hazırladıqdan sonra Generate menyusunu açın, domeni seçin və New Content və ya New Article düyməsinə klikləyin.
Dil
Hər məzmun sorğusu üçün yalnız bir dil seçilir.
Mövzu
Aydın bir mövzu yazın. Source URL istəyə bağlıdır.
Şəkil
Şəkli aktiv və ya deaktiv edin və Light və ya Dark mövzusunu seçin.
Ödəniş və yaradılma
Xərc cüzdandan çıxılır və məqalə yaradılması başlayır.
Preview, SEO Report və JSON Output-u yoxlayın
Məqalə yaradıldıqdan sonra keyfiyyət yoxlanışı və yayımlama hazırlığı üçün bir neçə vacib çıxış mövcuddur.
Preview
Məqalə mətnini və yaradılmış şəkli göstərir. Bu bölmə yayımlamadan əvvəl yekun keyfiyyəti yoxlamaq üçün faydalıdır.
SEO Report
SEO Score, title, meta description, canonical, açar sözlər, schema, başlıqlar, linklər, şəkillər və OpenGraph daxil olmaqla başa düşülən SEO hesabatı göstərir.
JSON Output
Texniki istifadəçilər üçün slug, html_body, schema, şəkil URL-ləri, tag-lər, kateqoriya, oxuma müddəti və yaradılma/yenilənmə tarixləri daxil olmaqla tam məqalə çıxışını göstərir.
Export API və Webhook dəqiq olaraq nə edir?
OxiRanker-də məqalə yaradıldıqdan sonra təyinat saytı onu Export API ilə oxuya bilər və ya məqalə hazır olduqda Webhook vasitəsilə bildiriş ala bilər.
Üsul 1: Export API
Təyinat saytı lazım olduqda Export Token ilə OxiRanker-dən məqalələri oxuya bilər. Bu üsul əl ilə sync, gündəlik sync, məqalə siyahısı səhifələri və tam məqalə JSON-unun alınması üçün istifadə olunur.
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/jsonÜsul 2: Webhook
Yeni məqalə yaradıldıqda və ya məqalə yeniləndikdə OxiRanker təyinat saytının endpoint-inə bildiriş göndərir. Webhook tam məqaləni göndərmir. O, yalnız məqalə identifikatorunu göndərir ki, təyinat saytı tam məqaləni Export JSON API-dən ala bilsin.
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"
}Məzmun saxlanması məsuliyyəti və Image Mirroring
Təyinat saytı yekun məqaləni və şəkilləri öz infrastrukturunda saxlamalıdır.
Təyinat saytı nəyi saxlamalıdır
Düzgün şəkil yolu nümunəsi
OxiRanker şəkil URL-lərini birbaşa və daimi istifadə etmək düzgün deyil. Şəkil endirilməli və məqalə URL-ləri təyinat saytının daxili URL-ləri ilə əvəz edilməlidir.
https://oxiranker.com/uploads/articles/3_5/cover.webphttps://example.com/uploads/site-articles/oxiranker/1/5/cover.webpExport Token və hazır endpoint-ləri alın
Export Token və istifadəyə hazır endpoint-lər məqalə səhifəsindən, Outputs tabından və API Output bölməsindən əldə olunur.
Export Token haradan alınır
https://oxiranker.com/en/pannel/generate/{domainId}/articles/{articleId}İstifadəyə hazır endpoint-lər
Eyni məqalə səhifəsində, API Output bölməsində Ready-to-use endpoints hazırlanmış URL-ləri göstərir.
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=0Webhook Secret və təyinat endpoint-i yaradın
Webhook Secret də məqalə səhifəsindən, Outputs tabından və API Output bölməsindən yaradılır.
Webhook Secret harada yaradılır?
Sabit təyinat endpoint-i
OxiRanker təyinat Webhook-u üçün yalnız aşağıdakı sabit yolu qəbul edir. Təyinat saytı bu endpoint-i öz backend-ində yaratmalıdır.
POST /api/oxiranker/webhook
https://example.com/api/oxiranker/webhookBu endpoint yalnız POST qəbul etməli, raw body-ni saxlamalı, timestamp və signature-i yoxlamalı və webhook.test üçün uğurlu cavab qaytarmalıdır.
Təyinat saytı üçün tələb olunan .env dəyərləri
OxiRanker təyinat saytının fayllarına və ya .env faylına girişə malik deyil. Təyinat saytının developer-i bu dəyərləri öz backend-ində saxlamalıdır.
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=50Təhlükəsizlik dəyərləri
Saxlama dəyərləri
Export API: List, JSON, HTML və ZIP
Təyinat saytı Export API-dən istifadə edərək məqalə siyahısını və ya hər məqalənin tam çıxışını qəbul edə bilər.
Domen üçün məqalə siyahısını alın
GET https://oxiranker.com/api/v1/users/domains/:domainId/export/articles?limit=20&offset=0
Authorization: Bearer YOUR_EXPORT_TOKEN
Accept: application/jsonlimit məqalələrin sayını müəyyən edir və hazırda 1 ilə 50 arasında məhdudlaşdırılıb. offset səhifələmə üçün istifadə olunur. lang istəyə bağlıdır və fa, en və ya tr kimi müəyyən dil üçün məqalələri almaq üçün istifadə olunur.
Tam məqaləni JSON kimi alın
GET https://oxiranker.com/api/v1/users/domains/:domainId/articles/:articleId/export/json
Authorization: Bearer YOUR_EXPORT_TOKEN
Accept: application/jsonBu, təyinat saytı üçün ən vacib çıxışdır. Təyinat saytı tam məqalə JSON-unu öz verilənlər bazasında saxlamalı və açıq göstərim üçün öz verilənlər bazasından oxumalıdır.
Hazır HTML alın
GET https://oxiranker.com/api/v1/users/domains/1/articles/5/export/html?canonical_mode=absolute&include_css=0
Authorization: Bearer YOUR_EXPORT_TOKENHTML hazır çıxış istəyən saytlar üçün uyğundur, lakin daha peşəkar yanaşma JSON-u saxlayıb təyinat saytının öz şablonu ilə render etməkdir.
ZIP çıxışı alın
GET https://oxiranker.com/api/v1/users/domains/1/articles/5/export/zip
Authorization: Bearer YOUR_EXPORT_TOKENZIP çıxışına article.json, meta.json, assets.json, schema.jsonld, index.html, version.txt və README.txt daxildir.
Webhook imzası və təhlükəsiz Webhook qəbulu
Təyinat saytı orijinal raw body istifadə edərək Webhook imzasını yoxlamalıdır.
Webhook header-ləri
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=...İmza formulu
payload_to_sign = timestamp + "." + rawBody
signature = "sha256=" + HMAC_SHA256(webhook_secret, payload_to_sign)Təyinat saytı əvvəlcə JSON-u parse edib sonra yenidən string-ə çevirərsə, sıra və ya boşluqlar dəyişə bilər və imza etibarsız ola bilər. İmza yoxlanışı üçün orijinal raw body istifadə olunmalıdır.
Webhook qəbul etmək üçün sadə Node.js kod nümunəsi
Bu Express nümunəsi raw body, timestamp, signature, webhook.test və article event-ləri ilə işləyir.
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,
});
});Təyinat saytı üçün tövsiyə olunan verilənlər bazası
Təyinat saytı məqalələri öz verilənlər bazasında saxlamalı və unique constraint ilə təkrarların qarşısını almalıdır.
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);UPSERT ilə təkrarların qarşısını alın
Məqalə hər Webhook üçün yenidən insert edilməməlidir. Əgər artıq mövcuddursa, yenilənməlidir.
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();Webhook-dan sonra tam məqaləni fetch edin və sync edin
Webhook gəldikdə, təyinat saytı Export API-dən tam məqaləni fetch etməli, şəkilləri mirror etməli və məqaləni UPSERT etməlidir.
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 və şəkil saxlanması qaydaları
Təyinat saytı şəkilləri endirməli, öz infrastrukturunda saxlamalı və məqalə URL-lərini əvəz etməlidir.
Image Mirroring qaydaları
Tövsiyə olunan saxlama yolu
/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.webpEtibarlılıq üçün gündəlik sync
Təkcə Webhook kifayət deyil. Təyinat saytı buraxılmış məqalələri bərpa etmək üçün gündəlik sync də etməlidir.
Gündəlik sync niyə lazımdır?
Gündəlik sync alqoritmi
Məqalələri təyinat saytında göstərin
Məqalələr site_articles-də saxlandıqdan sonra təyinat saytı hər açıq göstərim üçün OxiRanker-ə qoşulmamalıdır. O, öz verilənlər bazasından oxumalıdır.
Blog siyahısı səhifəsi
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;Məqalə detal səhifəsi
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 və HTML render
Təyinat saytı SEO məlumatlarını öz verilənlər bazasından oxumalı və məqalə səhifəsində render etməlidir.
SEO sahələri
HTML render
<div
className="article-content"
dangerouslySetInnerHTML={{ __html: article.html_body }}
/>Təyinat saytının ciddi təhlükəsizlik siyasəti varsa, HTML-i saxlamazdan və ya göstərməzdən əvvəl sanitize edə bilər.
RTL və LTR
Hər məqalədə lang, dir və is_rtl sahələri var. dir rtl-dirsə, məqalə səhifəsi sağdan sola render edilməlidir.
<article dir="rtl">
...
</article>Ümumi xətalar və mənaları
Təyinat saytı xətaları ingiliscə, aydın və başa düşülən olmalıdır ki, troubleshooting sürətlə aparılsın.
Export API xətaları
Webhook xətaları
Yekun API və Webhook test siyahısı
Tətbiqdən sonra bu testlər bağlantını əvvəldən sona qədər yoxlayır.
Export Token və JSON testi
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/jsonWebhook testi
Təyinat Webhook-unu Postman ilə imzasız çağırırsınızsa, təhlükəsizlik xətası almalısınız. Bu, endpoint-in aktiv olduğunu və təhlükəsizliyi yoxladığını göstərir.
{
"error": "missing_webhook_signature",
"message": "Webhook signature header is required."
}Sonra OxiRanker daxilində Test Webhook düyməsinə klikləyin. Receiver webhook.test-i dəstəkləyirsə, response_status 200 olmalıdır.
Düzgün tətbiq üçün vacib qaydalar
API inteqrasiyasında sistemin təhlükəsiz, sabit və saxlanıla bilən qalması üçün bu qaydalara əməl edilməlidir.
Tam API axını xülasəsi
Bu addımları tamamladıqdan sonra xüsusi saytınız OxiRanker tərəfindən yaradılmış məqalələri təhlükəsiz şəkildə qəbul edə, saxlaya, mirror edə və yayımlaya bilər.