OXIRANKER DEVELOPER DOCS

Ҳуҷҷатҳои OxiRanker

Маркази тозаву равшани ҳуҷҷатҳо барои пайваст кардани вебсайти шумо ба сервисҳои OxiRanker. Аз интегратсияи WordPress ё интегратсияи API оғоз кунед ва баъд роҳнаморо қадам ба қадам пайравӣ кунед.

Ҳуҷҷатҳои расмӣFull-feature V1Барои соҳибони сайтҳо, таҳиягарон ва дастаҳои интегратсия
Менюи ҳуҷҷатҳо
Аз меню як роҳнамо интихоб кунед. Ин саҳифа маркази асосии ҳуҷҷатҳои OxiRanker аст.
Сервиси API

Сервиси API

Export API ва Webhook-ро барои сайтҳои фармоишӣ, custom CMS platforms, Next.js, Laravel, Node.js, PHP ё backend-ҳои Python истифода баред.

Domain
Тасдиқшуда
Content
Омода
API
Webhook
Огоҳии муҳими нигоҳдорӣ

OxiRanker барои destination website permanent hosting нест

OxiRanker барои content, images ё exported files-и destination website кафолати permanent hosting намедиҳад. OxiRanker user-content hosting service нест ва generated content, cover images, Open Graph images ё output files метавонанд дар infrastructure-и OxiRanker зиёда аз 90 рӯз боқӣ намонанд.

Бинобар ин, баъд аз гирифтани output, destination website бояд final article, images, SEO data, Open Graph data ва Schema data-ро дар database, server, storage ё CDN-и худ store кунад ва барои public display ба OxiRanker URLs вобаста набошад.

Видеои омӯзишӣ

Видеои омӯзишии пайвастшавӣ

Пеш аз пайваст кардани WordPress ё implement кардани интегратсияи API, ин видеоро ҳамчун роҳнамои визуалӣ истифода баред.

API Output

Output flow-ро қадам ба қадам implement кунед

Аввал Export Token ва Webhook Secret-ро дар backend-и худ store кунед, баъд Webhook receiver, database, UPSERT logic, Image Mirroring ва daily sync-ро пурра кунед.

ҚАДАМ 01

Сабти ном ва сохтани domain дар OxiRanker

Аввал дар OxiRanker сабти ном кунед, ба account-и худ sign in кунед ва аз бахши Domains ба Add Domain клик кунед.

Формати дурусти domain

Дар майдони Domain name танҳо root domain-ро ворид кунед. Суроға набояд http, https ё www дошта бошад.

example.com

Форматҳои нодуруст

Ин форматҳоро дар майдони domain ворид накунед.

https://example.com
http://example.com
www.example.com
Баъд аз сохтани domain
Агар формати domain valid бошад, OxiRanker domain-ро месозад ва шуморо ба қадами domain ownership verification мебарад.
ҚАДАМ 02

Тасдиқи моликияти domain

Дар ин қадам бояд исбот кунед, ки domain ба шумо тааллуқ дорад ё шумо барои идоракунии он access доред.

DNS TXT

Тавсияшуда

Ба DNS-и domain як TXT record илова кунед. Барои Cloudflare, ин одатан содатарин method аст.

DNS Name:
_yourdomain-verification.example.com

TXT Value:
acb1****29803

HTTP File

Як text file-ро дар path-и .well-known-и вебсайти худ ҷойгир кунед, то OxiRanker онро verify карда тавонад.

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

File Content:
220daaa37****42730f27

Meta Tag

Як meta tag-ро дар head section-и homepage-и вебсайти худ ҷойгир кунед.

<meta name="yourdomain-verification" content="190208d****d275b65" />
Эзоҳи муҳими DNS
Баъд аз илова кардани DNS record, махсусан дар Cloudflare, мумкин аст 2 то 3 дақиқа интизор шудан лозим шавад. Сипас дар ҳамон OxiRanker page ба Confirm клик кунед.
Муҳлати эътибори verification token
Domain verification values маҳдудияти вақт доранд. Айни ҳол verification values одатан барои 1440 minutes valid мебошанд.
ҚАДАМ 03

Гирифтани token-ҳои ройгон баъд аз тасдиқи domain-и аввал

Баъд аз verify кардани domain-и аввал, OxiRanker барои initial testing ба account-и шумо 1000 free tokens илова мекунад.

Танҳо барои domain-и аввал
Ин тӯҳфа танҳо барои аввалин verified domain илова мешавад ва барои domain-ҳои баъдӣ дубора илова намешавад.
ҚАДАМ 04

Танзими забонҳо ва Blog URL

Дар бахши Blog URLs муайян кунед, ки вебсайти шумо single-language аст ё multilingual ва кадом languages бояд барои generated articles истифода шаванд.

Интихоби забонҳо

OxiRanker 24 languages-ро дастгирӣ мекунад. Шумо метавонед вобаста ба сохтори вебсайти худ як ё якчанд languages интихоб кунед.

Ворид кардани Blog URL

Барои ҳар language, blog URL-и ҳамон language-ро ворид кунед. Агар вебсайти шумо multilingual бошад, ҳар language path-ро ҷудогона ворид кунед.

https://example.com/fa/blog
https://example.com/en/blog
Сохтори URL
Агар вебсайти шумо сохтори дигар дошта бошад, blog URL-и воқеии вебсайти худро ворид кунед. Нуктаи муҳим ин аст, ки final URL бояд ҳамон path бошад, ки articles бояд дар он publish шаванд.
ҚАДАМ 05

Илова кардани keywords барои ҳар забон

Дар tab-и Keywords, барои ҳар language ҳадди ақал 3 ва то 50 keywords илова кунед.

Қоидаи асосӣ

Keywords-и ҳар language бояд бо ҳамон language ворид шаванд. Агар article language English бошад, keywords ҳам бояд English бошанд.

Чӣ тавр keywords илова карда мешаванд

Баъд аз навиштани ҳар keyword, барои илова кардани он Enter-ро пахш кунед. Баъд аз пур кардани ҳамаи keywords, ба Save клик кунед.

Хатои маъмул
Агар барои English language keywords-и Turkish ворид кунед, мумкин аст Turkish phrases дар English content пайдо шаванд. Keyword language бояд бо article language мувофиқ бошад.
ҚАДАМ 06

Пур кардани Domain Article Profile

Article Profile ба OxiRanker мегӯяд, ки brand-и шумо чист, барои кӣ менависад, кадом tone-ро бояд истифода барад ва аз кадом topics бояд дурӣ кунад.

Industry

Market ё business category-ро муайян мекунад. Мисол: real estate consulting ва property buying and selling дар Dubai.

Business description

Шарҳ медиҳад, ки business чӣ мефурӯшад, ба кӣ хизмат мекунад, чӣ гуна кор мекунад ва чӣ чиз онро фарқ мекунад.

Audience

Муайян мекунад, ки content бояд бо кӣ сухан гӯяд. Ҳар audience метавонад ҷудогона save шавад.

Tone

Style ва voice-и generated content-ро идора мекунад, монанди formal, educational, simple, technical ё sales-oriented.

Content goal

Муайян мекунад, ки articles бояд ба чӣ ноил шаванд, монанди ҷалби customers, зиёд кардани traffic ё омӯзонидани users.

Мавзӯъҳои манъшуда

Ҳар чизе, ки brand набояд дар бораи он сухан гӯяд ё набояд бо он алоқаманд шавад, дар ин ҷо ворид мешавад.

Optional CTA

CTA маънои action-еро дорад, ки мехоҳед reader баъд аз хондани article анҷом диҳад. Агар мутмаин набошед, ки кадом text suitable аст, беҳтар аст ин field-ро empty гузоред.

Multilingual profile
Агар вебсайти шумо multilingual бошад, кофист profile-ро танҳо дар як language пур кунед. OxiRanker метавонад versions-и лозимро барои languages-и дигар бар асоси ҳамон profile омода кунад.
ҚАДАМ 07

Линкҳои дохилӣ ва берунӣ

OxiRanker метавонад internal links ва external links-ро ба таври intelligent дар articles татбиқ кунад.

Internal links

Ин option ба таври default enabled аст ва беҳтар аст enabled боқӣ монад. Internal linking ба search engines кӯмак мекунад, ки content structure-и вебсайти шуморо беҳтар фаҳманд.

Дар article-и аввал, internal links одатан сохта намешаванд, зеро ҳанӯз previous article вуҷуд надорад. Аз article-и дуюм ба баъд, system метавонад internal linking иҷро кунад.

External links

External links ба official ва trusted sources илова мешаванд ва ҳамчун nofollow marked мешаванд, то SEO authority нохоста интиқол наёбад.

ҚАДАМ 08

Санҷидани profile quality бо AI

Баъд аз пур кардани Article Profile, OxiRanker profile quality-ро review мекунад.

85+
Minimum score-и лозим барои save
95
Excellent score, ки одатан баъд аз improvements ба даст меояд
AI
Professional suggestions барои ҳар field
Агар score аз 85 кам бошад
System final saving-ро иҷозат намедиҳад ва барои ҳар problematic field explanations ва improvement suggestions нишон медиҳад. Шумо метавонед suggestions-ро apply кунед ва дубора save кунед.
ҚАДАМ 09

Пайваст кардани Telegram барои article summaries

Баъд аз profile бомуваффақият save шуд, шумо метавонед фиристодани article summaries ба Telegram channel-ро enable кунед.

1

Илова кардани bot

@Oxiranker_Bot-ро ба Telegram channel-и худ илова кунед ва онро Admin кунед.

2

Ворид кардани channel username

Channel name-ро бо @ ворид кунед. Мисол: @my_channel

3

Enable summary delivery

Баъд аз сабти channel-и valid, article summary delivery-ро enable кунед.

Фоидаи Telegram
Агар enabled бошад, баъд аз content generation, article summary бо image ва link ба Telegram channel фиристода мешавад. Ин метавонад traffic paths ба вебсайти шуморо қавитар кунад.
ҚАДАМ 10

Танзими automatic article engine

Дар tab-и Automatic article generation, шумо метавонед automatic article generation engine-ро идора кунед.

Smart topic selection

System аз keywords ва Article Profile истифода мебарад, то topics-и suitable, unique ва publishable интихоб кунад.

Brand-aware generation

Tone, audience, content goal ва brand restrictions ҳангоми article generation риоя мешаванд.

Scheduled publishing

Monthly capacity дар тӯли моҳ тақсим мешавад, то content publishing табиӣтар ва consistent ба назар расад.

Monthly capacity

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

30 articles маънои тақрибан як article дар рӯзро дорад. 15 articles маънои тақрибан як рӯз дар миёнро дорад. 10 articles маънои тақрибан ҳар ду-се рӯзро дорад. 7 articles маънои тақрибан ҳар се-чор рӯзро дорад.

Article length ва image

Article length метавонад Short, Standard ё Long бошад. Standard барои аксари вебсайтҳо balanced choice аст.

Article images метавонанд enabled ё disabled шаванд ва image theme метавонад Light ё Dark бошад.

Эзоҳ барои API websites
Automatic engine articles-ро дар дохили OxiRanker generate мекунад. Барои интиқоли ҳар article ба custom website-и худ баъд аз generation, шумо бояд Export API, Webhook, database storage ва Image Mirroring-ро implement кунед.
ҚАДАМ 11

Сохтани article-и аввал аз Generate

Баъд аз омода кардани domain, Generate menu-ро кушоед, domain-ро интихоб кунед ва ба New Content ё New Article клик кунед.

1

Language

Барои ҳар content request танҳо як language интихоб мешавад.

2

Topic

Як topic-и равшан нависед. Source URL optional аст.

3

Image

Image-ро enable ё disable кунед ва theme-и Light ё Dark интихоб кунед.

4

Payment ва generation

Cost аз wallet deducted мешавад ва article generation оғоз мешавад.

Generation time
Article ва image generation одатан тақрибан 2 то 3 дақиқа вақт мегирад. Баъд аз анҷом, article дар дохили OxiRanker visible мешавад.
Source URL
Агар Source URL ворид шавад, OxiRanker онро барои беҳтар фаҳмидани topic истифода мебарад, аммо copied content generate намекунад. System барои сохтани unique content designed шудааст.
ҚАДАМ 12

Review кардани Preview, SEO Report ва JSON Output

Баъд аз generated шудани article, якчанд output-и муҳим барои quality review ва publishing preparation дастрас мешавад.

Preview

Article text ва generated image-ро нишон медиҳад. Ин section барои review кардани final quality пеш аз publishing муфид аст.

SEO Report

Як SEO report-и understandable нишон медиҳад, ки SEO Score, title, meta description, canonical, keywords, schema, headings, links, images ва OpenGraph-ро дар бар мегирад.

JSON Output

Full article output-ро барои technical users нишон медиҳад, аз ҷумла slug, html_body, schema, image URLs, tags, category, reading time ва created/updated timestamps.

Маънои output-ҳо
Ин output-ҳо нишон медиҳанд, ки article танҳо plain text нест. Он structured, SEO-ready content аст, ки барои WordPress, CMS, API ва publishing systems омода шудааст.
ҚАДАМ 13

Export API ва Webhook дақиқ чӣ кор мекунанд?

Баъд аз generated шудани article дар OxiRanker, destination website метавонад онро бо Export API read кунад ё ҳангоми ready шудани article тавассути Webhook notified шавад.

Method 1: Export API

Destination website метавонад articles-ро аз OxiRanker бо Export Token ҳар вақте лозим бошад read кунад. Ин method барои manual sync, daily sync, article list pages ва fetching full article JSON истифода мешавад.

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

Method 2: Webhook

Вақте new article created мешавад ё article updated мешавад, OxiRanker destination website endpoint-ро notify мекунад. Webhook full article намефиристад. Он танҳо article identifier мефиристад, то destination website метавонад full article-ро аз Export JSON API fetch кунад.

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"
}
Эзоҳи хеле муҳим
Webhook танҳо як lightweight message аст. Destination website набояд full article-ро дар дохили Webhook expect кунад. Баъд аз гирифтани domain_id ва article_id, он бояд complete article-ро аз Export JSON API fetch кунад ва дар database-и худ store кунад.
ҚАДАМ 14

Content storage responsibility ва Image Mirroring

Destination website бояд final article ва images-ро дар infrastructure-и худ store кунад.

Destination website бояд чиро store кунад

Full article text дар destination website database
Main article image дар destination server, storage ё CDN
Open Graph image дар destination server, storage ё CDN
SEO, OpenGraph ва Article Schema data
raw_detail_payload ва raw_list_item барои preserve кардани original output

Мисоли дурусти image path

Истифодаи OxiRanker image URLs ба таври directly ва permanently дуруст нест. Image бояд download шавад ва article URLs бояд бо internal URLs-и destination website replace шаванд.

https://oxiranker.com/uploads/articles/3_5/cover.webp
https://example.com/uploads/site-articles/oxiranker/1/5/cover.webp
OxiRanker барои destination website permanent hosting нест
OxiRanker барои permanently storing-и output content ва images барои destination website масъул нест. OxiRanker user-content hosting service нест ва generated content, cover images, Open Graph images ё output files метавонанд дар OxiRanker infrastructure зиёда аз 90 days боқӣ намонанд. Бинобар ин, destination website бояд final article, images, SEO data, OpenGraph ва Schema-ро дар database, server, storage ё CDN-и худ store кунад.
ҚАДАМ 15

Гирифтани Export Token ва ready endpoints

Export Token ва ready-to-use endpoints аз article page, Outputs tab ва API Output section дастрасанд.

Export Token аз куҷо гирифта мешавад

Article-и аввалро дар OxiRanker create кунед.
Ҳамон article page-ро open кунед.
Outputs tab-ро open кунед.
API Output section-ро open кунед.
Аз Token management token create кунед.
Full token танҳо як бор shown мешавад.
Token-ро дар destination website backend ё .env store кунед.
https://oxiranker.com/en/pannel/generate/{domainId}/articles/{articleId}

Ready-to-use endpoints

Дар ҳамон article page, дар дохили API Output section, Ready-to-use endpoints prepared URLs-ро нишон медиҳад.

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
Rotating Token
Агар Export Token дар OxiRanker panel rotated шавад, previous token expires мешавад. New value бояд immediately old value-ро дар destination website .env ё backend environment replace кунад ва destination backend бояд restarted ё redeployed шавад.
ҚАДАМ 16

Сохтани Webhook Secret ва destination endpoint

Webhook Secret ҳам аз article page, Outputs tab ва API Output section сохта мешавад.

Webhook Secret дар куҷо сохта мешавад?

Article page-ро open кунед.
Outputs tab-ро open кунед.
API Output section-ро open кунед.
Дар Webhook Secret section secret create кунед.
Full secret танҳо як бор shown мешавад.
Secret-ро дар destination website .env store кунед.

Fixed destination endpoint

OxiRanker барои destination Webhook танҳо fixed path-и зеринро қабул мекунад. Destination website бояд ин endpoint-ро дар backend-и худ create кунад.

POST /api/oxiranker/webhook

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

Ин endpoint бояд танҳо POST-ро accept кунад, raw body-ро preserve кунад, timestamp ва signature-ро verify кунад ва барои webhook.test successful response return кунад.

Active events
Дар current version, events fixed ҳастанд: article.created ва article.updated. Барои testing connection, event value webhook.test фиристода мешавад.
ҚАДАМ 17

Required .env values барои destination website

OxiRanker ба destination website files ё .env access надорад. Destination website developer бояд ин values-ро дар backend-и худ store кунад.

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

Security values

OXIRANKER_EXPORT_API_TOKEN
Export API token бояд танҳо дар backend stored шавад.
OXIRANKER_WEBHOOK_SECRET
Webhook Secret бояд танҳо дар backend stored шавад.
OXIRANKER_WEBHOOK_MAX_SKEW_MS
Maximum allowed time difference барои Webhook. 300000 маънои 5 minutes дорад.

Storage values

SITE_ARTICLE_IMAGE_UPLOAD_DIR
Physical path барои storing mirrored images дар destination server.
SITE_ARTICLE_IMAGE_MAX_BYTES
Maximum allowed image size барои download ва storage.
PUBLIC_BASE_URL
Public destination website URL, ки барои create кардани final image ва article URLs истифода мешавад.
Frontend security
Export Token ва Webhook Secret набояд дар frontend code, user-visible JavaScript, localStorage ё window object ҷойгир шаванд.
ҚАДАМ 18

Export API: List, JSON, HTML ва ZIP

Destination website метавонад Export API-ро барои receive кардани article list ё full output-и ҳар article истифода барад.

Гирифтани list of articles барои domain

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

limit number of articles-ро муайян мекунад ва айни ҳол аз 1 то 50 limited аст. offset барои pagination истифода мешавад. lang optional аст ва барои fetch кардани articles-и specific language мисли fa, en ё tr истифода мешавад.

Гирифтани full article ҳамчун JSON

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

Ин муҳимтарин output барои destination website аст. Destination website бояд full article JSON-ро дар database-и худ store кунад ва барои public display аз database-и худ read кунад.

Гирифтани ready HTML

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 барои websites, ки ready output мехоҳанд, suitable аст, аммо approach-и professional-тар ин аст, ки JSON store шавад ва бо template-и худи destination website render шавад.

Гирифтани ZIP output

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

ZIP output article.json, meta.json, assets.json, schema.jsonld, index.html, version.txt ва README.txt-ро дар бар мегирад.

ҚАДАМ 19

Webhook Signature ва secure Webhook receiving

Destination website бояд Webhook signature-ро бо истифода аз original raw body verify кунад.

Webhook headers

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

Signature formula

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

Агар destination website аввал JSON-ро parse кунад ва баъд онро дубора stringify кунад, order ё spacing метавонад тағйир ёбад ва signature invalid шавад. Барои signature verification бояд original raw body истифода шавад.

Timing-safe comparison
Signatures-ро бо expected === received муқоиса накунед. Дар Node.js беҳтар аст crypto.timingSafeEqual истифода шавад.
ҚАДАМ 20

Simple Node.js code sample барои receiving Webhook

Ин Express sample raw body, timestamp, signature, webhook.test ва article events-ро handle мекунад.

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,
  });
});
ҚАДАМ 21

Recommended destination website database

Destination website бояд articles-ро дар database-и худ store кунад ва бо unique constraint duplicates-ро prevent кунад.

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 note
Дар PostgreSQL барои Persian ё Unicode text simple quotes истифода мешаванд. Чизе мисли N'...' лозим нест ва набояд истифода шавад.
ҚАДАМ 22

Prevent duplicates бо UPSERT

Article набояд барои ҳар Webhook дубора inserted шавад. Агар он аллакай exists бошад, бояд updated шавад.

Main key барои preventing duplicates
Муҳимтарин constraint барои preventing duplicates ин аст: 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();
ҚАДАМ 23

Fetch кардани full article ва sync баъд аз Webhook

Вақте Webhook мерасад, destination website бояд full article-ро аз Export API fetch кунад, images-ро mirror кунад ва article-ро UPSERT кунад.

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,
  });
}
ҚАДАМ 24

Image Mirroring ва image storage rules

Destination website бояд images-ро download кунад, дар infrastructure-и худ store кунад ва article URLs-ро replace кунад.

Image Mirroring rules

Танҳо HTTPS URLs бояд accepted шаванд.
File бояд воқеан image бошад.
content-type бояд бо image/ оғоз шавад.
Empty files набояд stored шаванд.
File size набояд аз limit зиёд бошад.
Агар cover ва og якхела бошанд, танҳо як бор download кунед.
Баъд аз mirroring, URLs-ро дар html_body ва article_schema replace кунед.

Recommended storage path

/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
ҚАДАМ 25

Daily sync барои reliability

Webhook alone кофӣ нест. Destination website бояд daily sync ҳам дошта бошад, то missed articles-ро recover кунад.

Чаро daily sync лозим аст?

Destination website метавонад temporarily down шавад.
Webhook метавонад timeout шавад.
Deployment ё network issues метавонанд рух диҳанд.
Article метавонад generated шавад, аммо Webhook-и он missed шавад.

Daily sync algorithm

Offset-ро аз 0 start кунед.
Limit 20 ё 50-ро истифода баред.
Article list-ро аз Export List API fetch кунед.
Барои ҳар item, Export JSON-и ҳамон article-ро fetch кунед.
Images-ро mirror кунед.
Article-ро бо UPSERT store кунед.
Offset-ро increase кунед ва то completed шудани total continue кунед.
ҚАДАМ 26

Намоиши articles дар destination website

Баъд аз storing articles дар site_articles, destination website набояд барои ҳар public display ба OxiRanker connect шавад. Он бояд аз database-и худ read кунад.

Blog list page

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;

Article detail page

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;
Recommended article URL
Барои multilingual websites, шумо метавонед pattern-и /{lang}/blog/{slug}-ро истифода баред. Мисол: /fa/blog/smart-internal-links
ҚАДАМ 27

SEO, Schema, RTL ва HTML rendering

Destination website бояд SEO data-ро аз database-и худ read кунад ва онро дар article page render кунад.

SEO fields

title барои page title
meta_description барои meta description
effective_canonical_url барои canonical
og_title ва og_description барои Open Graph
og_image_url ё cover_image_url барои social preview image
article_schema барои JSON-LD

HTML rendering

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

Агар destination website strict security policy дошта бошад, метавонад HTML-ро пеш аз saving ё пеш аз displaying sanitize кунад.

RTL ва LTR

Ҳар article fields-и lang, dir ва is_rtl дорад. Агар dir rtl бошад, article page бояд right-to-left rendered шавад.

<article dir="rtl">
  ...
</article>
ҚАДАМ 28

Common errors ва маънои онҳо

Destination website errors бояд English, clear ва understandable бошанд, то troubleshooting зуд анҷом шавад.

Export API errors

Domain export token мавҷуд нест.
Export API token фиристода нашудааст. Authorization: Bearer YOUR_EXPORT_TOKEN бояд фиристода шавад.
Domain export token invalid аст.
Token wrong аст, rotated шудааст, ба ин domain алоқаманд нест ё domainId wrong аст.
Token required scope надорад.
Token required scope надорад. New tokens бояд articles:read ва exports:read дошта бошанд.
lang invalid аст.
Қимати lang дар query parameter incorrectly фиристода шудааст.

Webhook errors

Webhook URL бояд HTTPS истифода кунад.
Webhook URL бояд бо https:// оғоз шавад.
Webhook signature invalid аст.
Webhook Secret wrong аст, secret rotated шудааст, raw body changed шудааст ё signature incorrectly ҳисоб шудааст.
Webhook timestamp expired ё invalid аст.
Timestamp old аст, дар future аст ё invalid аст. Server clock ва NTP-ро санҷед.
Webhook test delivery ноком шуд.
Destination website successful status 200 то 299 return накард, timed out шуд ё webhook.test-ро correctly handle накард.
ҚАДАМ 29

Final API ва Webhook testing checklist

Баъд аз implementation, ин tests connection-ро аз beginning то end verify мекунанд.

Export Token ва 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

Агар destination Webhook-ро бо Postman бе signature call кунед, бояд security error receive кунед. Ин маънои онро дорад, ки endpoint active аст ва security-ро checks мекунад.

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

Сипас дар дохили OxiRanker ба Test Webhook клик кунед. Агар receiver webhook.test-ро supports кунад, response_status бояд 200 бошад.

Баъд аз rotation
Агар Webhook Secret ё Export Token rotated шавад, new value-ро фавран copy кунед, онро дар destination website .env replace кунед ва destination backend-ро restart ё redeploy кунед.
ҚАДАМ 30

Important rules барои correct implementation

Ин rules бояд дар API integration риоя шаванд, то system secure, stable ва maintainable бимонад.

Export Token бояд танҳо дар backend stored шавад.
Webhook Secret бояд танҳо дар backend stored шавад.
Destination Webhook endpoint бояд дақиқ /api/oxiranker/webhook бошад.
Webhook URL бояд HTTPS истифода кунад.
Webhook signature ва timestamp бояд ҳамеша verified шаванд.
Articles бояд бо UPSERT stored шаванд, на бо simple insert.
Барои prevent duplicates unique constraint required аст.
Images бояд аз OxiRanker downloaded шуда дар destination website stored шаванд.
Destination website набояд ба OxiRanker image URLs permanently depend кунад.
Image URLs бояд дар html_body ва article_schema replaced шаванд.
Агар Webhook fails шавад, daily sync бояд missed article-ро recover кунад.
Errors, ки ба users ё logs shown мешаванд, бояд English ва understandable бошанд.
Агар article archived бошад, automatic sync набояд онро дубора publish кунад.
raw_detail_payload ва raw_list_item бояд preserved шаванд, то original output data гум нашавад.
Агар Export Token ё Webhook Secret rotated шавад, new value бояд дар .env replaced шавад ва backend restarted ё redeployed шавад.
ҚАДАМ 31

Complete API flow summary

Баъд аз анҷоми ин steps, custom website-и шумо метавонад articles-и generated дар OxiRanker-ро securely receive, store, mirror ва publish кунад.

Сабти ном дар OxiRanker
Илова кардани domain
Verify domain ownership
Интихоби languages ва илова кардани Blog URL
Илова кардани keywords барои ҳар language
Пур кардани Article Profile
Танзими Telegram агар лозим бошад
Танзими automatic article engine
Create кардани article-и аввал аз Generate
Review кардани Preview ва SEO Report
Гирифтани Export Token аз API Output
Гирифтани ready Export API endpoints
Гирифтани Webhook Secret
Create кардани fixed /api/oxiranker/webhook endpoint дар destination backend
Store кардани Token ва Secret дар destination website .env
Create кардани site_articles table ва unique constraint
Implement кардани full article fetching аз Export JSON
Implement кардани Image Mirroring
Implement кардани UPSERT
Implement кардани daily sync
Render кардани article, SEO, OG ва Schema аз destination database
Run кардани final Export API ва Webhook tests