OXIRANKER DEVELOPER DOCS

OxiRanker दस्तावेज़ीकरण

अपनी वेबसाइट को OxiRanker सेवाओं से जोड़ने के लिए एक साफ़ documentation center. WordPress integration या API integration से शुरू करें, फिर guide को step by step follow करें.

आधिकारिक दस्तावेज़ीकरणFull-feature V1Site owners, developers और integration teams के लिए
Documentation menu
Menu से एक guide चुनें. यह पेज OxiRanker का मुख्य documentation center है.
API service

API service

Custom websites, custom CMS platforms, Next.js, Laravel, Node.js, PHP या Python backends के लिए Export API और Webhook का उपयोग करें.

Domain
Verified
Content
Ready
API
Webhook
महत्वपूर्ण storage notice

OxiRanker destination website के लिए permanent hosting नहीं है

OxiRanker destination website के content, images या exported files के लिए permanent hosting guarantee प्रदान नहीं करता. OxiRanker user-content hosting service नहीं है, और generated content, cover images, Open Graph images या output files OxiRanker infrastructure पर 90 दिनों से अधिक नहीं रह सकते.

इसलिए, output receive करने के बाद, destination website को final article, images, SEO data, Open Graph data और Schema data को अपने database, server, storage या CDN में store करना चाहिए, और public display के लिए OxiRanker URLs पर निर्भर नहीं रहना चाहिए.

Training video

Connection training video

WordPress connect करने या API integration implement करने से पहले इस video को visual walkthrough की तरह इस्तेमाल करें.

API Output

Output flow को step by step implement करें

पहले Export Token और Webhook Secret को अपने backend में store करें, फिर Webhook receiver, database, UPSERT logic, Image Mirroring और daily sync पूरा करें.

STEP 01

OxiRanker में register करें और domain बनाएं

सबसे पहले, OxiRanker में register करें, अपने account में sign in करें और Domains section से Add Domain पर click करें.

सही domain format

Domain name field में केवल root domain enter करें. Address में http, https या www शामिल नहीं होना चाहिए.

example.com

गलत formats

Domain field में ये formats enter न करें.

https://example.com
http://example.com
www.example.com
Domain बनाने के बाद
यदि domain format valid है, तो OxiRanker domain बनाता है और आपको domain ownership verification step पर ले जाता है.
STEP 02

Domain ownership verify करें

इस step में, आपको यह साबित करना होगा कि domain आपका है या आपके पास उसे manage करने की access है.

DNS TXT

अनुशंसित

Domain DNS में TXT record add करें. Cloudflare के लिए, यह आमतौर पर सबसे सरल method है.

DNS Name:
_yourdomain-verification.example.com

TXT Value:
acb1****29803

HTTP File

अपनी website के .well-known path में एक text file रखें ताकि OxiRanker उसे verify कर सके.

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

File Content:
220daaa37****42730f27

Meta Tag

अपनी website homepage के head section के अंदर एक meta tag रखें.

<meta name="yourdomain-verification" content="190208d****d275b65" />
महत्वपूर्ण DNS note
DNS record add करने के बाद, खासकर Cloudflare में, आपको 2 से 3 मिनट wait करना पड़ सकता है. फिर उसी OxiRanker page पर Confirm click करें.
Verification token lifetime
Domain verification values की time limit होती है. वर्तमान में, verification values आमतौर पर 1440 minutes के लिए valid होती हैं.
STEP 03

पहले domain को verify करने के बाद free tokens receive करें

पहले domain को verify करने के बाद, OxiRanker initial testing के लिए आपके account में 1000 free tokens add करता है.

केवल पहले domain के लिए
यह gift केवल पहले verified domain के लिए add होता है और बाद के domains के लिए दोबारा add नहीं होता.
STEP 04

Languages और Blog URL configure करें

Blog URLs section में define करें कि आपकी website single-language है या multilingual और generated articles के लिए कौन सी languages इस्तेमाल होनी चाहिए.

Languages चुनें

OxiRanker 24 languages support करता है. आप अपनी website structure के आधार पर एक या कई languages चुन सकते हैं.

Blog URL enter करें

हर language के लिए उस language का blog URL enter करें. अगर आपकी website multilingual है, तो हर language path अलग-अलग enter करें.

https://example.com/fa/blog
https://example.com/en/blog
URL structure
अगर आपकी website की structure अलग है, तो अपनी website का वास्तविक blog URL enter करें. महत्वपूर्ण बात यह है कि final URL वही path होना चाहिए जहाँ articles publish होने चाहिए.
STEP 05

हर language के लिए keywords add करें

Keywords tab में, हर language के लिए कम से कम 3 और अधिकतम 50 keywords add करें.

मुख्य नियम

हर language के keywords उसी language में enter होने चाहिए. अगर article language English है, तो keywords भी English होने चाहिए.

Keywords कैसे add करें

हर keyword type करने के बाद, उसे add करने के लिए Enter दबाएं. सभी keywords complete करने के बाद, Save click करें.

Common mistake
अगर आप English language के लिए Turkish keywords enter करते हैं, तो English content में Turkish phrases दिखाई दे सकते हैं. Keyword language को article language से match करना चाहिए.
STEP 06

Domain Article Profile complete करें

Article Profile OxiRanker को बताता है कि आपका brand क्या है, वह किसके लिए लिखता है, उसे कौन सा tone इस्तेमाल करना चाहिए और किन topics से बचना चाहिए.

Industry

Market या business category define करता है. Example: Dubai में real estate consulting और property buying/selling.

Business description

बताता है कि business क्या बेचता है, किसे serve करता है, कैसे काम करता है और उसे अलग क्या बनाता है.

Audience

Define करता है कि content किससे बात करे. हर audience अलग से save की जा सकती है.

Tone

Generated content की style और voice control करता है, जैसे formal, educational, simple, technical या sales-oriented.

Content goal

Define करता है कि articles क्या achieve करें, जैसे customers attract करना, traffic बढ़ाना या users को educate करना.

Forbidden topics

Brand को जिन चीज़ों के बारे में बात नहीं करनी चाहिए या जिनसे associate नहीं होना चाहिए, वे यहाँ enter की जाती हैं.

Optional CTA

CTA का मतलब वह action है जो आप चाहते हैं कि reader article पढ़ने के बाद करे. अगर आप sure नहीं हैं कि कौन सा text suitable है, तो यह field empty छोड़ना बेहतर है.

Multilingual profile
अगर आपकी website multilingual है, तो आपको profile केवल एक language में complete करनी होती है. OxiRanker उसी profile के आधार पर अन्य languages के लिए required versions prepare कर सकता है.
STEP 07

Internal और external linking

OxiRanker articles के अंदर internal और external links को intelligently apply कर सकता है.

Internal links

यह option default रूप से enabled है और इसे enabled रखना बेहतर है. Internal linking search engines को आपकी website के content structure को बेहतर समझने में मदद करती है.

पहले article में, internal links आमतौर पर create नहीं होते क्योंकि अभी कोई previous article नहीं होता. दूसरे article से आगे, system internal linking perform कर सकता है.

External links

External links official और trusted sources में add किए जाते हैं और nofollow के रूप में marked होते हैं ताकि SEO authority unintentionally transfer न हो.

STEP 08

AI से profile quality check करें

Article Profile complete करने के बाद, OxiRanker profile quality review करता है.

85+
Save करने के लिए required minimum score
95
Improvements के बाद आमतौर पर achieved excellent score
AI
हर field के लिए professional suggestions
अगर score 85 से कम है
System final saving allow नहीं करता और हर problematic field के लिए explanations और improvement suggestions दिखाता है. आप suggestions apply करके फिर से save कर सकते हैं.
STEP 09

Article summaries के लिए Telegram connect करें

Profile successfully save करने के बाद, आप Telegram channel पर article summaries भेजना enable कर सकते हैं.

1

Bot add करें

@Oxiranker_Bot को अपने Telegram channel में add करें और उसे Admin बनाएं.

2

Channel username enter करें

Channel name को @ के साथ enter करें. Example: @my_channel

3

Summary delivery enable करें

Valid channel register करने के बाद, article summary delivery enable करें.

Telegram benefit
अगर enabled हो, तो content generation के बाद article summary image और link के साथ Telegram channel पर भेजी जाती है. यह आपकी website तक traffic paths को strengthen कर सकता है.
STEP 10

Automatic article engine configure करें

Automatic article generation tab में, आप automatic article generation engine manage कर सकते हैं.

Smart topic selection

System keywords और Article Profile का उपयोग करके suitable, unique और publishable topics select करता है.

Brand-aware generation

Article generation के दौरान tone, audience, content goal और brand restrictions का ध्यान रखा जाता है.

Scheduled publishing

Monthly capacity पूरे month में distribute होती है ताकि content publishing अधिक natural और 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 ज्यादातर websites के लिए balanced choice है.

Article images enabled या disabled की जा सकती हैं, और image theme Light या Dark चुनी जा सकती है.

API websites के लिए note
Automatic engine articles को OxiRanker के अंदर generate करता है. Generation के बाद हर article को अपनी custom website पर move करने के लिए, आपको Export API, Webhook, database storage और Image Mirroring implement करना होगा.
STEP 11

Generate से पहला article बनाएं

Domain prepare करने के बाद, Generate menu खोलें, domain चुनें और New Content या New Article click करें.

1

Language

हर content request के लिए केवल एक language select होती है.

2

Topic

एक clear topic लिखें. Source URL optional है.

3

Image

Image enable या disable करें और Light या Dark theme चुनें.

4

Payment और generation

Cost wallet से deduct होता है और article generation start होती है.

Generation time
Article और image generation आमतौर पर लगभग 2 से 3 मिनट लेती है. Completion के बाद, article OxiRanker के अंदर visible होता है.
Source URL
अगर Source URL enter किया जाता है, तो OxiRanker topic को बेहतर समझने के लिए उसका उपयोग करता है, लेकिन copied content generate नहीं करता. System unique content create करने के लिए designed है.
STEP 12

Preview, SEO Report और JSON Output review करें

Article generate होने के बाद, quality review और publishing preparation के लिए कई important outputs available होते हैं.

Preview

Article text और generated image दिखाता है. यह section publishing से पहले final quality review करने के लिए useful है.

SEO Report

SEO Score, title, meta description, canonical, keywords, schema, headings, links, images और OpenGraph सहित understandable SEO report दिखाता है.

JSON Output

Technical users के लिए full article output दिखाता है, जिसमें slug, html_body, schema, image URLs, tags, category, reading time और created/updated timestamps शामिल हैं.

Outputs का अर्थ
ये outputs दिखाते हैं कि article सिर्फ plain text नहीं है. यह WordPress, CMS, API और publishing systems के लिए तैयार structured, SEO-ready content है.
STEP 13

Export API और Webhook वास्तव में क्या करते हैं?

OxiRanker में article generate होने के बाद, destination website उसे Export API से read कर सकती है या article ready होने पर Webhook के माध्यम से notify हो सकती है.

Method 1: Export API

Destination website जरूरत पड़ने पर Export Token के साथ OxiRanker से articles read कर सकती है. यह method manual sync, daily sync, article list pages और full article JSON fetch करने के लिए use होती है.

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

जब नया article create होता है या article update होता है, OxiRanker destination website endpoint को notify करता है. Webhook full article नहीं भेजता. यह केवल article identifier भेजता है ताकि destination website Export JSON API से full article 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"
}
बहुत महत्वपूर्ण note
Webhook केवल एक lightweight message है. Destination website को Webhook के अंदर full article expect नहीं करना चाहिए. domain_id और article_id receive करने के बाद, उसे Export JSON API से complete article fetch करके अपने database में store करना चाहिए.
STEP 14

Content storage responsibility और Image Mirroring

Destination website को final article और images अपनी own infrastructure पर store करने चाहिए.

Destination website को क्या store करना चाहिए

Destination website database में full article text
Destination server, storage या CDN पर main article image
Destination server, storage या CDN पर Open Graph image
SEO, OpenGraph और Article Schema data
Original output preserve करने के लिए raw_detail_payload और raw_list_item

Correct image path example

OxiRanker image URLs को directly और permanently use करना सही नहीं है. Image download होनी चाहिए और article URLs को destination website के internal URLs से 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 destination website के output content और images को permanently store करने के लिए responsible नहीं है. 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 करना चाहिए.
STEP 15

Export Token और ready endpoints प्राप्त करें

Export Token और ready-to-use endpoints article page, Outputs tab और API Output section से available हैं.

Export Token कहाँ से प्राप्त करें

OxiRanker में पहला article create करें.
उस article page को open करें.
Outputs tab open करें.
API Output section open करें.
Token management से token create करें.
Full token केवल एक बार दिखाया जाता है.
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 display करता है.

https://oxiranker.com/api/v1/users/domains/1/export/articles?limit=20&offset=0
https://oxiranker.com/api/v1/users/domains/1/articles/1/export/json
https://oxiranker.com/api/v1/users/domains/1/articles/1/export/html?canonical_mode=absolute&include_css=0
Token rotate करना
अगर Export Token को OxiRanker panel में rotate किया जाता है, तो previous token expire हो जाता है. New value को destination website .env या backend environment में old value को immediately replace करना चाहिए, और destination backend को restart या redeploy करना चाहिए.
STEP 16

Webhook Secret और destination endpoint create करें

Webhook Secret भी article page, Outputs tab और API Output section से create होता है.

Webhook Secret कहाँ create होता है?

Article page open करें.
Outputs tab open करें.
API Output section open करें.
Webhook Secret section में secret create करें.
Full secret केवल एक बार दिखाया जाता है.
Secret को destination website .env में store करें.

Fixed destination endpoint

OxiRanker destination Webhook के लिए केवल निम्न fixed path accept करता है. 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. Connection test करने के लिए, event value webhook.test भेजी जाती है.
STEP 17

Destination website के लिए required .env values

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 में store होना चाहिए.
OXIRANKER_WEBHOOK_SECRET
Webhook Secret केवल backend में store होना चाहिए.
OXIRANKER_WEBHOOK_MAX_SKEW_MS
Webhook के लिए maximum allowed time difference. 300000 का मतलब 5 minutes है.

Storage values

SITE_ARTICLE_IMAGE_UPLOAD_DIR
Destination server पर mirrored images store करने के लिए physical path.
SITE_ARTICLE_IMAGE_MAX_BYTES
Download और storage के लिए maximum allowed image size.
PUBLIC_BASE_URL
Final image और article URLs create करने के लिए इस्तेमाल होने वाला public destination website URL.
Frontend security
Export Token और Webhook Secret को frontend code, user-visible JavaScript, localStorage या window object के अंदर नहीं रखना चाहिए.
STEP 18

Export API: List, JSON, HTML और ZIP

Destination website Export API का उपयोग article list या हर article का full output receive करने के लिए कर सकती है.

Domain के लिए articles की list प्राप्त करें

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

limit articles की संख्या define करता है और वर्तमान में 1 से 50 के बीच limited है. offset pagination के लिए use होता है. lang optional है और fa, en या tr जैसी specific language के articles fetch करने के लिए use होता है.

Full article को JSON के रूप में प्राप्त करें

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

यह destination website के लिए सबसे important output है. 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 के लिए suitable है जो ready output चाहती हैं, लेकिन अधिक professional approach JSON store करना और destination website के own template से 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 शामिल हैं.

STEP 19

Webhook Signature और secure Webhook receiving

Destination website को original raw body का उपयोग करके Webhook signature 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 से compare न करें. Node.js में crypto.timingSafeEqual use करना बेहतर है.
STEP 20

Webhook receive करने के लिए simple Node.js code sample

यह 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,
  });
});
STEP 21

Recommended destination website database

Destination website को articles अपने own database में store करने चाहिए और unique constraint से duplicates रोकने चाहिए.

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 use होते हैं. N'...' जैसी चीज़ की जरूरत नहीं है और उसे use नहीं करना चाहिए.
STEP 22

UPSERT से duplicates रोकें

Article को हर Webhook के लिए दोबारा insert नहीं करना चाहिए. अगर यह पहले से exists करता है, तो इसे update करना चाहिए.

Duplicates रोकने के लिए main key
Duplicates रोकने के लिए सबसे important constraint है: 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();
STEP 23

Webhook के बाद full article fetch और sync करें

जब Webhook आता है, destination website को Export API से full article 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,
  });
}
STEP 24

Image Mirroring और image storage rules

Destination website को images download करनी चाहिए, उन्हें अपनी own infrastructure पर store करना चाहिए और article URLs replace करने चाहिए.

Image Mirroring rules

केवल HTTPS URLs accept होने चाहिए.
File वास्तव में image होनी चाहिए.
content-type image/ से start होना चाहिए.
Empty files store नहीं होनी चाहिए.
File size limit से अधिक नहीं होनी चाहिए.
अगर cover और og same हैं, तो केवल एक बार download करें.
Mirroring के बाद, html_body और article_schema के अंदर URLs 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
STEP 25

Reliability के लिए daily sync

Webhook alone enough नहीं है. Destination website के पास missed articles recover करने के लिए daily sync भी होना चाहिए.

Daily sync क्यों जरूरी है?

Destination website temporarily down हो सकती है.
Webhook timeout हो सकता है.
Deployment या network issues हो सकते हैं.
Article generate हो सकता है लेकिन उसका Webhook miss हो सकता है.

Daily sync algorithm

offset को 0 से start करें.
limit 20 या 50 use करें.
Export List API से article list fetch करें.
हर item के लिए, उस article का Export JSON fetch करें.
Images mirror करें.
Article को UPSERT के साथ store करें.
offset increase करें और total complete होने तक continue करें.
STEP 26

Destination website पर articles display करें

site_articles में articles store करने के बाद, destination website को हर public display के लिए OxiRanker से connect नहीं करना चाहिए. उसे अपने own 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 के लिए, आप /{lang}/blog/{slug} pattern use कर सकते हैं. Example: /fa/blog/smart-internal-links
STEP 27

SEO, Schema, RTL और HTML rendering

Destination website को अपने own database से SEO data read करके article page पर render करना चाहिए.

SEO fields

page title के लिए title
meta description के लिए meta_description
canonical के लिए effective_canonical_url
Open Graph के लिए og_title और og_description
social preview image के लिए og_image_url या cover_image_url
JSON-LD के लिए article_schema

HTML rendering

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

अगर destination website की strict security policy है, तो वह HTML को save करने से पहले या display करने से पहले sanitize कर सकती है.

RTL और LTR

हर article में lang, dir और is_rtl fields होते हैं. अगर dir rtl है, तो article page को right-to-left render करना चाहिए.

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

Common errors और उनके अर्थ

Destination website errors English, clear और understandable होने चाहिए ताकि troubleshooting जल्दी हो सके.

Export API errors

Domain export token missing है.
Export API token भेजा नहीं गया. Authorization: Bearer YOUR_EXPORT_TOKEN भेजना जरूरी है.
Domain export token invalid है.
Token गलत है, rotate हो चुका है, इस domain से related नहीं है या domainId गलत है.
Token में required scope नहीं है.
Token में required scope नहीं है. New tokens में articles:read और exports:read होना चाहिए.
lang invalid है.
Query parameter में lang value गलत तरीके से भेजी गई.

Webhook errors

Webhook URL को HTTPS use करना चाहिए.
Webhook URL https:// से start होना चाहिए.
Webhook signature invalid है.
Webhook Secret गलत है, secret rotate हो चुका है, raw body बदल गया है या signature गलत calculate किया गया है.
Webhook timestamp expired या invalid है.
Timestamp पुराना है, future में है या invalid है. Server clock और NTP check करें.
Webhook test delivery failed.
Destination website ने successful 200 से 299 status return नहीं किया, timed out हो गई या webhook.test को correctly handle नहीं किया.
STEP 29

Final API और Webhook testing checklist

Implementation के बाद, ये tests beginning से end तक connection 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 मिलना चाहिए. इसका मतलब है कि endpoint active है और security check करता है.

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

फिर OxiRanker के अंदर Test Webhook click करें. अगर receiver webhook.test support करता है, तो response_status 200 होना चाहिए.

Rotation के बाद
अगर Webhook Secret या Export Token rotate होता है, तो new value तुरंत copy करें, उसे destination website .env में replace करें और destination backend restart या redeploy करें.
STEP 30

Correct implementation के लिए important rules

API integration में ये rules follow होने चाहिए ताकि system secure, stable और maintainable रहे.

Export Token केवल backend में store होना चाहिए.
Webhook Secret केवल backend में store होना चाहिए.
Destination Webhook endpoint बिल्कुल /api/oxiranker/webhook होना चाहिए.
Webhook URL को HTTPS use करना चाहिए.
Webhook signature और timestamp हमेशा verify होने चाहिए.
Articles UPSERT के साथ store होने चाहिए, simple insert से नहीं.
Duplicates रोकने के लिए unique constraint required है.
Images OxiRanker से download होकर destination website पर store होनी चाहिए.
Destination website को OxiRanker image URLs पर permanently depend नहीं करना चाहिए.
Image URLs को html_body और article_schema के अंदर replace करना चाहिए.
अगर Webhook fail हो, तो daily sync missed article recover करे.
Users या logs को दिखाए जाने वाले errors English और understandable होने चाहिए.
अगर article archived है, तो automatic sync उसे दोबारा publish नहीं करे.
raw_detail_payload और raw_list_item preserve होने चाहिए ताकि original output data lost न हो.
अगर Export Token या Webhook Secret rotate हो, तो new value .env में replace होनी चाहिए और backend restart या redeploy होना चाहिए.
STEP 31

Complete API flow summary

इन steps को complete करने के बाद, आपकी custom website OxiRanker में generated articles को securely receive, store, mirror और publish कर सकती है.

OxiRanker में register करें
Domain add करें
Domain ownership verify करें
Languages चुनें और Blog URL add करें
हर language के लिए keywords add करें
Article Profile complete करें
जरूरत हो तो Telegram configure करें
Automatic article engine configure करें
Generate से पहला article create करें
Preview और SEO Report review करें
API Output से Export Token प्राप्त करें
Ready Export API endpoints प्राप्त करें
Webhook Secret प्राप्त करें
Destination backend में fixed /api/oxiranker/webhook endpoint create करें
Destination website .env में Token और Secret store करें
site_articles table और unique constraint create करें
Export JSON से full article fetching implement करें
Image Mirroring implement करें
UPSERT implement करें
Daily sync implement करें
Destination database से article, SEO, OG और Schema render करें
Final Export API और Webhook tests run करें