OXIRANKER DEVELOPER DOCS

OxiRanker دستاویزات

اپنی ویب سائٹ کو OxiRanker سروسز سے جوڑنے کے لیے ایک صاف دستاویزاتی مرکز۔ WordPress انضمام یا API انضمام سے شروع کریں، پھر گائیڈ کو قدم بہ قدم فالو کریں۔

سرکاری دستاویزاتFull-feature V1سائٹ مالکان، developers اور integration teams کے لیے
دستاویزات مینو
مینو سے ایک گائیڈ منتخب کریں۔ یہ صفحہ OxiRanker کا مرکزی دستاویزاتی مرکز ہے۔
API سروس

API سروس

Custom websites، custom CMS platforms، Next.js، Laravel، Node.js، PHP یا Python backend کے لیے Export API اور Webhook استعمال کریں۔

Domain
Verified
Content
Ready
API
Webhook
Storage سے متعلق اہم نوٹس

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 حاصل کرنے کے بعد destination website کو final article، images، SEO data، Open Graph data اور Schema data اپنی database، server، storage یا CDN میں store کرنا چاہیے، اور public display کے لیے OxiRanker URLs پر depend نہیں کرنا چاہیے۔

Training video

Connection training video

WordPress جوڑنے یا API integration implement کرنے سے پہلے اس video کو visual walkthrough کے طور پر استعمال کریں۔

API Output

Output flow کو قدم بہ قدم 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 پر کلک کریں۔

Domain کا درست format

Domain name field میں صرف root domain درج کریں۔ Address میں http، https یا www شامل نہیں ہونا چاہیے۔

example.com

غلط formats

Domain field میں یہ formats درج نہ کریں۔

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 شامل کریں۔ 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 نوٹ
DNS record شامل کرنے کے بعد، خاص طور پر Cloudflare میں، آپ کو 2 سے 3 minutes انتظار کرنا پڑ سکتا ہے۔ پھر اسی OxiRanker page پر Confirm پر کلک کریں۔
Verification token کی مدت
Domain verification values کی time limit ہوتی ہے۔ اس وقت verification values عام طور پر 1440 minutes تک valid ہوتے ہیں۔
STEP 03

پہلا domain verify ہونے کے بعد مفت token حاصل کریں

پہلا domain verify کرنے کے بعد OxiRanker initial testing کے لیے آپ کے account میں 1000 free tokens شامل کرتا ہے۔

صرف پہلے domain کے لیے
یہ gift صرف پہلے verified domain کے لیے شامل ہوتا ہے اور اگلے domains کے لیے دوبارہ شامل نہیں کیا جاتا۔
STEP 04

زبانیں اور Blog URL configure کریں

Blog URLs section میں define کریں کہ آپ کی website single-language ہے یا multilingual، اور generated articles کے لیے کون سی languages استعمال ہونی چاہئیں۔

زبانیں منتخب کریں

OxiRanker 24 languages کو support کرتا ہے۔ آپ اپنی website structure کے مطابق ایک یا کئی languages منتخب کر سکتے ہیں۔

Blog URL درج کریں

ہر language کے لیے اس language کا blog URL درج کریں۔ اگر آپ کی website multilingual ہے تو ہر language path الگ درج کریں۔

https://example.com/fa/blog
https://example.com/en/blog
URL structure
اگر آپ کی website کا structure مختلف ہے تو اپنی website کا اصل blog URL درج کریں۔ اہم بات یہ ہے کہ final URL وہ path ہو جہاں articles publish ہونے ہیں۔
STEP 05

ہر language کے لیے keywords شامل کریں

Keywords tab میں ہر language کے لیے کم از کم 3 اور زیادہ سے زیادہ 50 keywords شامل کریں۔

بنیادی rule

ہر language کے keywords اسی language میں درج ہونے چاہئیں۔ اگر article language English ہے تو keywords بھی English ہونے چاہئیں۔

Keywords کیسے شامل کریں

ہر keyword لکھنے کے بعد اسے شامل کرنے کے لیے Enter دبائیں۔ تمام keywords مکمل کرنے کے بعد Save پر کلک کریں۔

عام غلطی
اگر آپ English language کے لیے Turkish keywords درج کریں تو English content میں Turkish phrases ظاہر ہو سکتے ہیں۔ Keyword language کو article language سے match ہونا چاہیے۔
STEP 06

Domain Article Profile مکمل کریں

Article Profile OxiRanker کو بتاتا ہے کہ آپ کا brand کیا ہے، کس کے لیے لکھتا ہے، کون سا tone استعمال کرنا چاہیے اور کن topics سے بچنا چاہیے۔

Industry

Market یا business category define کرتا ہے۔ مثال: Dubai میں real estate consulting اور property buying and selling۔

Business description

یہ explain کرتا ہے کہ business کیا sell کرتا ہے، کس کو 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 کرنا۔

منع شدہ topics

وہ سب کچھ جس کے بارے میں brand کو بات نہیں کرنی چاہیے یا جس سے associate نہیں ہونا چاہیے، یہاں درج کیا جاتا ہے۔

Optional CTA

CTA اس action کو کہتے ہیں جو آپ چاہتے ہیں کہ reader article پڑھنے کے بعد کرے۔ اگر آپ کو یقین نہیں کہ کون سا text suitable ہے، تو بہتر ہے یہ field empty چھوڑ دیں۔

Multilingual profile
اگر آپ کی website multilingual ہے تو profile صرف ایک language میں مکمل کرنا کافی ہے۔ OxiRanker اسی profile کی بنیاد پر دوسری languages کے لیے required versions تیار کر سکتا ہے۔
STEP 07

Internal اور external links

OxiRanker articles کے اندر internal links اور external links کو intelligent طریقے سے apply کر سکتا ہے۔

Internal links

یہ option default طور پر enabled ہوتا ہے اور اسے enabled رکھنا بہتر ہے۔ Internal linking search engines کو آپ کی website کے content structure کو بہتر سمجھنے میں مدد دیتی ہے۔

پہلے article میں internal links عام طور پر نہیں بنائے جاتے کیونکہ ابھی previous article موجود نہیں ہوتا۔ دوسرے article سے system internal linking perform کر سکتا ہے۔

External links

External links official اور trusted sources پر add کیے جاتے ہیں اور nofollow کے طور پر marked ہوتے ہیں تاکہ SEO authority غیر ارادی طور پر منتقل نہ ہو۔

STEP 08

AI سے profile quality check کریں

Article Profile مکمل کرنے کے بعد OxiRanker profile quality review کرتا ہے۔

85+
Save کے لیے required minimum score
95
Improvements کے بعد عام طور پر حاصل ہونے والا excellent score
AI
ہر field کے لیے professional suggestions
اگر score 85 سے کم ہو
System final saving کی اجازت نہیں دیتا اور ہر problematic field کے لیے explanations اور improvement suggestions دکھاتا ہے۔ آپ suggestions apply کر کے دوبارہ save کر سکتے ہیں۔
STEP 09

Article summaries کے لیے Telegram connect کریں

Profile کامیابی سے save ہونے کے بعد آپ Telegram channel پر article summaries بھیجنا enable کر سکتے ہیں۔

1

Bot شامل کریں

@Oxiranker_Bot کو اپنے Telegram channel میں شامل کریں اور اسے Admin بنائیں۔

2

Channel username درج کریں

Channel name کو @ کے ساتھ درج کریں۔ مثال: @my_channel

3

Summary delivery enable کریں

Valid channel register کرنے کے بعد article summary delivery enable کریں۔

Telegram کا فائدہ
اگر enabled ہو تو content generation کے بعد article summary image اور link کے ساتھ Telegram channel پر بھیجی جاتی ہے۔ یہ آپ کی website کی طرف traffic paths کو مضبوط کر سکتا ہے۔
STEP 10

Automatic article engine configure کریں

Automatic article generation tab میں آپ automatic article generation engine کو manage کر سکتے ہیں۔

Smart topic selection

System suitable، unique اور publishable topics منتخب کرنے کے لیے keywords اور Article Profile استعمال کرتا ہے۔

Brand-aware generation

Article generation کے دوران tone، audience، content goal اور brand restrictions کو respect کیا جاتا ہے۔

Scheduled publishing

Monthly capacity کو پورے مہینے میں 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 کے لیے نوٹ
Automatic engine articles کو OxiRanker کے اندر generate کرتا ہے۔ Generation کے بعد ہر article کو اپنی custom website پر منتقل کرنے کے لیے Export API، Webhook، database storage اور Image Mirroring implement کرنا ہوگا۔
STEP 11

Generate سے پہلا article بنائیں

Domain تیار کرنے کے بعد Generate menu کھولیں، domain منتخب کریں اور New Content یا New Article پر کلک کریں۔

1

Language

ہر content request کے لیے صرف ایک language منتخب ہوتی ہے۔

2

Topic

واضح topic لکھیں۔ Source URL optional ہے۔

3

Image

Image enable یا disable کریں اور Light یا Dark theme منتخب کریں۔

4

Payment اور generation

Cost wallet سے deducted ہوتی ہے اور article generation شروع ہو جاتا ہے۔

Generation time
Article اور image generation عام طور پر تقریباً 2 سے 3 minutes لیتا ہے۔ مکمل ہونے کے بعد article OxiRanker کے اندر visible ہوتا ہے۔
Source URL
اگر Source URL درج کیا گیا ہو تو OxiRanker اسے topic کو بہتر سمجھنے کے لیے استعمال کرتا ہے، لیکن copied content generate نہیں کرتا۔ System unique content بنانے کے لیے designed ہے۔
STEP 12

Preview، SEO Report اور JSON Output review کریں

Article generated ہونے کے بعد quality review اور publishing preparation کے لیے کئی important outputs دستیاب ہوتے ہیں۔

Preview

Article text اور generated image دکھاتا ہے۔ یہ section publishing سے پہلے final quality review کرنے کے لیے مفید ہے۔

SEO Report

ایک understandable SEO report دکھاتا ہے جس میں SEO Score، title، meta description، canonical، keywords، schema، headings، links، images اور OpenGraph شامل ہیں۔

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 generated ہونے کے بعد destination website اسے Export API سے read کر سکتی ہے یا article ready ہونے پر Webhook کے ذریعے notified ہو سکتی ہے۔

Method 1: Export API

Destination website ضرورت کے وقت Export Token کے ذریعے OxiRanker سے articles read کر سکتی ہے۔ یہ method manual sync، daily sync، article list pages اور full article JSON fetching کے لیے استعمال ہوتی ہے۔

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 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"
}
بہت اہم نوٹ
Webhook صرف lightweight message ہے۔ Destination website کو Webhook کے اندر full article expect نہیں کرنا چاہیے۔ domain_id اور article_id حاصل کرنے کے بعد اسے Export JSON API سے complete article fetch کر کے اپنی database میں store کرنا چاہیے۔
STEP 14

Content storage responsibility اور Image Mirroring

Destination website کو final article اور images اپنی 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

درست image path کی مثال

OxiRanker image URLs کو directly اور permanently استعمال کرنا درست نہیں ہے۔ 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 storing کرنے کا ذمہ دار نہیں ہے۔ 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 سے دستیاب ہیں۔

Export Token کہاں سے حاصل کریں

OxiRanker میں پہلا article 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
Token rotation
اگر Export Token OxiRanker panel میں rotated ہو جائے تو previous token expires ہو جاتا ہے۔ New value کو destination website .env یا backend environment میں old value کو فوراً replace کرنا چاہیے، اور destination backend کو restart یا redeploy ہونا چاہیے۔
STEP 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۔ Connection testing کے لیے 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 میں stored ہونا چاہیے۔
OXIRANKER_WEBHOOK_SECRET
Webhook Secret صرف backend میں stored ہونا چاہیے۔
OXIRANKER_WEBHOOK_MAX_SKEW_MS
Webhook کے لیے maximum allowed time difference۔ 300000 کا مطلب 5 minutes ہے۔

Storage values

SITE_ARTICLE_IMAGE_UPLOAD_DIR
Destination server پر mirrored images storing کے لیے 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 کے لیے list of articles حاصل کریں

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 define کرتا ہے اور فی الحال 1 سے 50 کے درمیان limited ہے۔ offset pagination کے لیے استعمال ہوتا ہے۔ lang optional ہے اور specific language کے articles جیسے fa، en یا tr fetch کرنے کے لیے استعمال ہوتا ہے۔

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 کے اپنے 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 استعمال کرنا بہتر ہے۔
STEP 20

Webhook receiving کے لیے 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 اپنی 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'...' جیسی چیز ضروری نہیں اور استعمال نہیں ہونی چاہیے۔
STEP 22

UPSERT سے duplicates prevent کریں

Article ہر Webhook کے لیے دوبارہ inserted نہیں ہونا چاہیے۔ اگر یہ پہلے سے exists ہے تو اسے updated ہونا چاہیے۔

Duplicates preventing کے لیے main key
Duplicates preventing کے لیے سب سے 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 کرنی چاہئیں، اپنی 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 کے بعد 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 کافی نہیں ہے۔ Destination website کے پاس missed articles recover کرنے کے لیے daily sync بھی ہونا چاہیے۔

Daily sync کیوں ضروری ہے؟

Destination website temporarily down ہو سکتی ہے۔
Webhook timeout ہو سکتا ہے۔
Deployment یا network issues ہو سکتے ہیں۔
Article generated ہو سکتا ہے مگر اس کا Webhook missed ہو سکتا ہے۔

Daily sync algorithm

offset کو 0 سے start کریں۔
limit 20 یا 50 استعمال کریں۔
Export List API سے article list fetch کریں۔
ہر item کے لیے اس article کا Export JSON fetch کریں۔
Images mirror کریں۔
Article کو UPSERT سے store کریں۔
offset increase کریں اور total completed ہونے تک continue کریں۔
STEP 26

Destination website پر articles دکھائیں

Articles کو site_articles میں storing کرنے کے بعد 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 کے لیے آپ /{lang}/blog/{slug} pattern استعمال کر سکتے ہیں۔ مثال: /fa/blog/smart-internal-links
STEP 27

SEO، Schema، RTL اور HTML rendering

Destination website کو SEO data اپنی database سے 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 کو saving سے پہلے یا displaying سے پہلے sanitize کر سکتی ہے۔

RTL اور LTR

ہر article میں lang، dir اور is_rtl fields ہوتے ہیں۔ اگر dir rtl ہو تو article page کو right-to-left rendered ہونا چاہیے۔

<article dir="rtl">
  ...
</article>
STEP 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 ہے۔
Query parameter میں lang value incorrectly بھیجی گئی ہے۔

Webhook errors

Webhook URL کو HTTPS استعمال کرنا چاہیے۔
Webhook URL کو https:// سے شروع ہونا چاہیے۔
Webhook signature invalid ہے۔
Webhook Secret wrong ہے، secret rotated ہے، raw body changed ہے یا signature incorrectly calculated ہے۔
Webhook timestamp expired یا invalid ہے۔
Timestamp old ہے، future میں ہے یا invalid ہے۔ Server clock اور NTP check کریں۔
Webhook test delivery ناکام ہو گئی۔
Destination website نے successful status 200 سے 299 return نہیں کیا، timed out ہوا یا webhook.test کو correctly handle نہیں کیا۔
STEP 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 support کرتا ہے تو response_status 200 ہونا چاہیے۔

Rotation کے بعد
اگر Webhook Secret یا Export Token rotated ہو جائے تو 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 میں stored ہونا چاہیے۔
Webhook Secret صرف backend میں stored ہونا چاہیے۔
Destination Webhook endpoint بالکل /api/oxiranker/webhook ہونا چاہیے۔
Webhook URL کو HTTPS استعمال کرنا چاہیے۔
Webhook signature اور timestamp ہمیشہ verified ہونے چاہئیں۔
Articles کو simple insert کے بجائے UPSERT سے stored ہونا چاہیے۔
Duplicates prevent کرنے کے لیے 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 کرنا چاہیے۔
Users یا logs کو shown errors 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 ہونا چاہیے۔
STEP 31

Complete API flow summary

یہ steps مکمل کرنے کے بعد آپ کی custom website OxiRanker میں generated articles کو securely receive، store، mirror اور publish کر سکتی ہے۔

OxiRanker میں register کریں
Domain شامل کریں
Domain ownership verify کریں
Languages منتخب کریں اور Blog URL شامل کریں
ہر language کے لیے keywords شامل کریں
Article Profile مکمل کریں
ضرورت ہو تو 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 کریں
Token اور Secret کو destination website .env میں 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 کریں