مستندات توسعه‌دهندگان OXIRANKER

مستندات OxiRanker

یک مرکز مستندات تمیز برای اتصال وب‌سایت شما به سرویس‌های OxiRanker. با یکپارچه‌سازی WordPress یا یکپارچه‌سازی API شروع کنید و سپس راهنما را مرحله‌به‌مرحله دنبال کنید.

مستندات رسمینسخه ۱.۰ کامل (فول‌فیچر)برای مالکان سایت، توسعه‌دهندگان و تیم‌های یکپارچه‌سازی
منوی مستندات
یک راهنما را از منو انتخاب کنید. این صفحه مرکز اصلی مستندات OxiRanker است.
سرویس API

سرویس API

از Export API و Webhook برای وب‌سایت‌های سفارشی، پلتفرم‌های CMS سفارشی، Next.js، Laravel، Node.js، PHP یا backendهای Python استفاده کنید.

دامنه
تأیید شده
محتوا
آماده
API
Webhook
اطلاعیه مهم ذخیره‌سازی

OxiRanker هاست دائمی برای وب‌سایت مقصد نیست

OxiRanker هیچ ضمانتی برای میزبانی دائمی محتوای وب‌سایت مقصد، تصاویر یا فایل‌های خروجی ارائه نمی‌دهد. OxiRanker سرویس میزبانی محتوای کاربران نیست و محتوای تولیدشده، تصاویر کاور، تصاویر Open Graph یا فایل‌های خروجی ممکن است بیش از ۹۰ روز روی زیرساخت OxiRanker باقی نمانند.

بنابراین، بعد از دریافت خروجی، وب‌سایت مقصد باید مقاله نهایی، تصاویر، داده‌های SEO، داده‌های Open Graph و داده‌های Schema را در دیتابیس، سرور، فضای ذخیره‌سازی یا CDN خودش ذخیره کند و برای نمایش عمومی به URLهای OxiRanker وابسته نباشد.

ویدیوی آموزشی

ویدیوی آموزش اتصال

قبل از اتصال WordPress یا پیاده‌سازی یکپارچه‌سازی API، از این ویدیو به‌عنوان راهنمای تصویری استفاده کنید.

خروجی API

جریان خروجی را مرحله‌به‌مرحله پیاده‌سازی کنید

ابتدا Export Token و Webhook Secret را در backend خود ذخیره کنید، سپس Webhook receiver، دیتابیس، منطق UPSERT، Image Mirroring و sync روزانه را کامل کنید.

مرحله 01

ثبت‌نام و ایجاد دامنه در OxiRanker

ابتدا در OxiRanker ثبت‌نام کنید، وارد حساب خود شوید و از بخش Domains روی Add Domain کلیک کنید.

فرمت صحیح دامنه

در فیلد Domain name فقط دامنه اصلی را وارد کنید. آدرس نباید شامل http، https یا www باشد.

example.com

فرمت‌های اشتباه

این فرمت‌ها را در فیلد دامنه وارد نکنید.

https://example.com
http://example.com
www.example.com
بعد از ایجاد دامنه
اگر فرمت دامنه معتبر باشد، OxiRanker دامنه را ایجاد می‌کند و شما را به مرحله تأیید مالکیت دامنه می‌برد.
مرحله 02

تأیید مالکیت دامنه

در این مرحله، باید ثابت کنید دامنه متعلق به شماست یا به مدیریت آن دسترسی دارید.

DNS TXT

پیشنهادی

یک TXT record به DNS دامنه اضافه کنید. برای Cloudflare، این معمولاً ساده‌ترین روش است.

DNS Name:
_yourdomain-verification.example.com

TXT Value:
acb1****29803

HTTP File

یک فایل متنی را در مسیر .well-known وب‌سایت خود قرار دهید تا OxiRanker بتواند آن را تأیید کند.

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

File Content:
220daaa37****42730f27

Meta Tag

یک meta tag را داخل بخش head صفحه اصلی وب‌سایت خود قرار دهید.

<meta name="yourdomain-verification" content="190208d****d275b65" />
نکته مهم DNS
بعد از اضافه کردن DNS record، مخصوصاً در Cloudflare، ممکن است لازم باشد ۲ تا ۳ دقیقه صبر کنید. سپس در همان صفحه OxiRanker روی Confirm کلیک کنید.
مدت اعتبار توکن تأیید
مقادیر تأیید دامنه محدودیت زمانی دارند. در حال حاضر، مقادیر تأیید معمولاً برای ۱۴۴۰ دقیقه معتبر هستند.
مرحله 03

دریافت توکن‌های رایگان بعد از تأیید اولین دامنه

بعد از تأیید اولین دامنه، OxiRanker برای تست اولیه ۱۰۰۰ توکن رایگان به حساب شما اضافه می‌کند.

فقط برای اولین دامنه
این هدیه فقط برای اولین دامنه تأییدشده اضافه می‌شود و برای دامنه‌های بعدی دوباره اضافه نمی‌شود.
مرحله 04

تنظیم زبان‌ها و Blog URL

در بخش Blog URLs مشخص کنید که وب‌سایت شما تک‌زبانه است یا چندزبانه، و کدام زبان‌ها باید برای مقاله‌های تولیدشده استفاده شوند.

انتخاب زبان‌ها

OxiRanker از ۲۴ زبان پشتیبانی می‌کند. بر اساس ساختار وب‌سایت خود می‌توانید یک یا چند زبان را انتخاب کنید.

وارد کردن Blog URL

برای هر زبان، Blog URL همان زبان را وارد کنید. اگر وب‌سایت شما چندزبانه است، مسیر هر زبان را جداگانه وارد کنید.

https://example.com/fa/blog
https://example.com/en/blog
ساختار URL
اگر وب‌سایت شما ساختار متفاوتی دارد، Blog URL واقعی وب‌سایت خود را وارد کنید. نکته مهم این است که URL نهایی باید همان مسیری باشد که مقاله‌ها باید در آن منتشر شوند.
مرحله 05

افزودن کلمات کلیدی برای هر زبان

در تب Keywords، برای هر زبان حداقل ۳ و حداکثر ۵۰ کلمه کلیدی اضافه کنید.

قانون اصلی

کلمات کلیدی هر زبان باید به همان زبان وارد شوند. اگر زبان مقاله انگلیسی است، کلمات کلیدی هم باید انگلیسی باشند.

نحوه افزودن کلمات کلیدی

بعد از تایپ هر کلمه کلیدی، برای افزودن آن Enter را فشار دهید. بعد از تکمیل همه کلمات کلیدی، روی Save کلیک کنید.

اشتباه رایج
اگر برای زبان انگلیسی کلمات کلیدی ترکی وارد کنید، ممکن است عبارت‌های ترکی در محتوای انگلیسی ظاهر شوند. زبان کلمات کلیدی باید با زبان مقاله مطابقت داشته باشد.
مرحله 06

تکمیل Article Profile دامنه

Article Profile به OxiRanker می‌گوید برند شما چیست، برای چه کسی می‌نویسد، چه لحنی باید استفاده کند و از چه موضوعاتی باید دوری کند.

صنعت

بازار یا دسته‌بندی کسب‌وکار را تعریف می‌کند. مثال: مشاوره املاک و خرید و فروش ملک در دبی.

توضیح کسب‌وکار

توضیح می‌دهد کسب‌وکار چه چیزی می‌فروشد، به چه کسانی خدمت می‌کند، چگونه کار می‌کند و چه چیزی آن را متفاوت می‌کند.

مخاطب

تعریف می‌کند محتوا باید با چه کسانی صحبت کند. هر مخاطب می‌تواند جداگانه ذخیره شود.

لحن

سبک و صدای محتوای تولیدشده را کنترل می‌کند، مانند رسمی، آموزشی، ساده، فنی یا فروش‌محور.

هدف محتوا

تعریف می‌کند مقاله‌ها باید چه چیزی را محقق کنند، مانند جذب مشتری، افزایش ترافیک یا آموزش کاربران.

موضوعات ممنوع

هر چیزی که برند نباید درباره آن صحبت کند یا نباید با آن مرتبط شود، در اینجا وارد می‌شود.

CTA اختیاری

CTA یعنی اقدامی که می‌خواهید خواننده بعد از خواندن مقاله انجام دهد. اگر مطمئن نیستید چه متنی مناسب است، بهتر است این فیلد را خالی بگذارید.

پروفایل چندزبانه
اگر وب‌سایت شما چندزبانه است، فقط کافی است پروفایل را در یک زبان کامل کنید. OxiRanker می‌تواند نسخه‌های لازم برای زبان‌های دیگر را بر اساس همان پروفایل آماده کند.
مرحله 07

لینک‌سازی داخلی و خارجی

OxiRanker می‌تواند لینک‌های داخلی و خارجی را به‌صورت هوشمند داخل مقاله‌ها اعمال کند.

لینک‌های داخلی

این گزینه به‌صورت پیش‌فرض فعال است و بهتر است فعال بماند. لینک‌سازی داخلی به موتورهای جستجو کمک می‌کند ساختار محتوای وب‌سایت شما را بهتر درک کنند.

در مقاله اول، معمولاً لینک داخلی ایجاد نمی‌شود چون هنوز مقاله قبلی وجود ندارد. از مقاله دوم به بعد، سیستم می‌تواند لینک‌سازی داخلی انجام دهد.

لینک‌های خارجی

لینک‌های خارجی به منابع رسمی و معتبر اضافه می‌شوند و به‌صورت nofollow علامت‌گذاری می‌شوند تا اعتبار سئو به‌صورت ناخواسته منتقل نشود.

مرحله 08

بررسی کیفیت پروفایل با AI

بعد از تکمیل Article Profile، OxiRanker کیفیت پروفایل را بررسی می‌کند.

85+
حداقل امتیاز موردنیاز برای ذخیره
95
امتیاز عالی که معمولاً بعد از بهبودها به دست می‌آید
AI
پیشنهادهای حرفه‌ای برای هر فیلد
اگر امتیاز کمتر از ۸۵ باشد
سیستم اجازه ذخیره نهایی نمی‌دهد و برای هر فیلد مشکل‌دار توضیحات و پیشنهادهای بهبود نمایش می‌دهد. می‌توانید پیشنهادها را اعمال کنید و دوباره ذخیره کنید.
مرحله 09

اتصال Telegram برای خلاصه مقاله‌ها

بعد از ذخیره موفق پروفایل، می‌توانید ارسال خلاصه مقاله‌ها به کانال Telegram را فعال کنید.

1

افزودن ربات

@Oxiranker_Bot را به کانال Telegram خود اضافه کنید و آن را Admin کنید.

2

وارد کردن نام کاربری کانال

نام کانال را با @ وارد کنید. مثال: @my_channel

3

فعال‌سازی ارسال خلاصه

بعد از ثبت یک کانال معتبر، ارسال خلاصه مقاله را فعال کنید.

مزیت Telegram
اگر فعال باشد، بعد از تولید محتوا، خلاصه مقاله همراه با تصویر و لینک به کانال Telegram ارسال می‌شود. این می‌تواند مسیرهای جذب ترافیک به وب‌سایت شما را تقویت کند.
مرحله 10

تنظیم موتور خودکار مقاله

در تب Automatic article generation، می‌توانید موتور تولید خودکار مقاله را مدیریت کنید.

انتخاب هوشمند موضوع

سیستم از کلمات کلیدی و Article Profile برای انتخاب موضوعات مناسب، منحصربه‌فرد و قابل انتشار استفاده می‌کند.

تولید آگاه از برند

لحن، مخاطب، هدف محتوا و محدودیت‌های برند هنگام تولید مقاله رعایت می‌شوند.

انتشار زمان‌بندی‌شده

ظرفیت ماهانه در طول ماه توزیع می‌شود تا انتشار محتوا طبیعی‌تر و منظم‌تر به نظر برسد.

ظرفیت ماهانه

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

۳۰ مقاله یعنی تقریباً روزی یک مقاله. ۱۵ مقاله یعنی تقریباً یک روز در میان. ۱۰ مقاله یعنی حدود هر دو تا سه روز. ۷ مقاله یعنی حدود هر سه تا چهار روز.

طول مقاله و تصویر

طول مقاله می‌تواند Short، Standard یا Long باشد. Standard برای بیشتر وب‌سایت‌ها انتخابی متعادل است.

تصاویر مقاله می‌توانند فعال یا غیرفعال شوند و تم تصویر می‌تواند Light یا Dark باشد.

نکته برای وب‌سایت‌های API
موتور خودکار مقاله‌ها را داخل OxiRanker تولید می‌کند. برای انتقال هر مقاله به وب‌سایت سفارشی خود بعد از تولید، باید Export API، Webhook، ذخیره‌سازی دیتابیس و Image Mirroring را پیاده‌سازی کنید.
مرحله 11

ایجاد اولین مقاله از Generate

بعد از آماده‌سازی دامنه، منوی Generate را باز کنید، دامنه را انتخاب کنید و روی New Content یا New Article کلیک کنید.

1

زبان

برای هر درخواست محتوا فقط یک زبان انتخاب می‌شود.

2

موضوع

یک موضوع واضح بنویسید. Source URL اختیاری است.

3

تصویر

تصویر را فعال یا غیرفعال کنید و تم Light یا Dark را انتخاب کنید.

4

پرداخت و تولید

هزینه از wallet کسر می‌شود و تولید مقاله شروع می‌شود.

زمان تولید
تولید مقاله و تصویر معمولاً حدود ۲ تا ۳ دقیقه طول می‌کشد. بعد از تکمیل، مقاله داخل OxiRanker قابل مشاهده است.
Source URL
اگر Source URL وارد شود، OxiRanker از آن برای درک بهتر موضوع استفاده می‌کند، اما محتوای کپی‌شده تولید نمی‌کند. سیستم برای ایجاد محتوای منحصربه‌فرد طراحی شده است.
مرحله 12

بررسی Preview، SEO Report و JSON Output

بعد از تولید مقاله، چند خروجی مهم برای بررسی کیفیت و آماده‌سازی انتشار در دسترس است.

Preview

متن مقاله و تصویر تولیدشده را نمایش می‌دهد. این بخش برای بررسی کیفیت نهایی قبل از انتشار مفید است.

SEO Report

یک گزارش قابل‌فهم سئو شامل SEO Score، title، meta description، canonical، keywords، schema، headings، links، images و OpenGraph را نمایش می‌دهد.

JSON Output

خروجی کامل مقاله را برای کاربران فنی نمایش می‌دهد، شامل slug، html_body، schema، URLهای تصویر، tags، category، زمان مطالعه و زمان‌های ایجاد/به‌روزرسانی.

معنی خروجی‌ها
این خروجی‌ها نشان می‌دهند مقاله فقط متن ساده نیست. مقاله یک محتوای ساختاریافته، آماده برای سئو و آماده برای WordPress، CMS، API و سیستم‌های انتشار است.
مرحله 13

Export API و Webhook دقیقاً چه کاری انجام می‌دهند؟

بعد از تولید یک مقاله در OxiRanker، وب‌سایت مقصد می‌تواند آن را با Export API بخواند یا وقتی مقاله آماده شد از طریق Webhook مطلع شود.

روش ۱: Export API

وب‌سایت مقصد می‌تواند هر زمان لازم بود مقاله‌ها را با Export Token از OxiRanker بخواند. این روش برای sync دستی، sync روزانه، صفحات لیست مقاله‌ها و دریافت 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

روش ۲: Webhook

وقتی یک مقاله جدید ایجاد می‌شود یا یک مقاله به‌روزرسانی می‌شود، OxiRanker endpoint وب‌سایت مقصد را مطلع می‌کند. Webhook مقاله کامل را ارسال نمی‌کند. فقط شناسه مقاله را ارسال می‌کند تا وب‌سایت مقصد بتواند مقاله کامل را از Export JSON API دریافت کند.

https://example.com/api/oxiranker/webhook
{
  "event": "article.created",
  "domain_id": "1",
  "article_id": "5",
  "request_id": "5",
  "lang": "fa",
  "slug": "smart-internal-links",
  "occurred_at": "2026-05-20T19:35:33.000Z"
}
نکته بسیار مهم
Webhook فقط یک پیام سبک است. وب‌سایت مقصد نباید انتظار داشته باشد مقاله کامل داخل Webhook باشد. بعد از دریافت domain_id و article_id، باید مقاله کامل را از Export JSON API دریافت کند و در دیتابیس خودش ذخیره کند.
مرحله 14

مسئولیت ذخیره‌سازی محتوا و Image Mirroring

وب‌سایت مقصد باید مقاله نهایی و تصاویر را روی زیرساخت خودش ذخیره کند.

وب‌سایت مقصد چه چیزهایی را باید ذخیره کند

متن کامل مقاله در دیتابیس وب‌سایت مقصد
تصویر اصلی مقاله روی سرور، فضای ذخیره‌سازی یا CDN مقصد
تصویر Open Graph روی سرور، فضای ذخیره‌سازی یا CDN مقصد
داده‌های SEO، OpenGraph و Article Schema
raw_detail_payload و raw_list_item برای حفظ خروجی اصلی

مثال صحیح مسیر تصویر

استفاده مستقیم و دائمی از URLهای تصاویر OxiRanker درست نیست. تصویر باید دانلود شود و URLهای مقاله باید با URLهای داخلی وب‌سایت مقصد جایگزین شوند.

https://oxiranker.com/uploads/articles/3_5/cover.webp
https://example.com/uploads/site-articles/oxiranker/1/5/cover.webp
OxiRanker هاست دائمی برای وب‌سایت مقصد نیست
OxiRanker مسئول ذخیره‌سازی دائمی محتوای خروجی و تصاویر برای وب‌سایت مقصد نیست. OxiRanker سرویس میزبانی محتوای کاربران نیست و محتوای تولیدشده، تصاویر کاور، تصاویر Open Graph یا فایل‌های خروجی ممکن است بیش از ۹۰ روز روی زیرساخت OxiRanker باقی نمانند. بنابراین، وب‌سایت مقصد باید مقاله نهایی، تصاویر، داده‌های SEO، OpenGraph و Schema را در دیتابیس، سرور، فضای ذخیره‌سازی یا CDN خودش ذخیره کند.
مرحله 15

دریافت Export Token و endpointهای آماده

Export Token و endpointهای آماده استفاده از صفحه مقاله، تب Outputs و بخش API Output در دسترس هستند.

Export Token را از کجا بگیریم

اولین مقاله را در OxiRanker ایجاد کنید.
صفحه همان مقاله را باز کنید.
تب Outputs را باز کنید.
بخش API Output را باز کنید.
از Token management یک token ایجاد کنید.
توکن کامل فقط یک بار نمایش داده می‌شود.
توکن را در backend وب‌سایت مقصد یا .env ذخیره کنید.
https://oxiranker.com/en/pannel/generate/{domainId}/articles/{articleId}

Endpointهای آماده استفاده

در همان صفحه مقاله، داخل بخش API Output، قسمت Ready-to-use endpoints آدرس‌های آماده را نمایش می‌دهد.

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
اگر Export Token در پنل OxiRanker چرخانده شود، توکن قبلی منقضی می‌شود. مقدار جدید باید فوراً جایگزین مقدار قدیمی در .env یا محیط backend وب‌سایت مقصد شود و backend مقصد باید restart یا redeploy شود.
مرحله 16

ایجاد Webhook Secret و endpoint مقصد

Webhook Secret نیز از صفحه مقاله، تب Outputs و بخش API Output ایجاد می‌شود.

Webhook Secret کجا ایجاد می‌شود؟

صفحه مقاله را باز کنید.
تب Outputs را باز کنید.
بخش API Output را باز کنید.
در بخش Webhook Secret یک secret ایجاد کنید.
secret کامل فقط یک بار نمایش داده می‌شود.
secret را در .env وب‌سایت مقصد ذخیره کنید.

Endpoint ثابت مقصد

OxiRanker فقط مسیر ثابت زیر را برای Webhook مقصد می‌پذیرد. وب‌سایت مقصد باید این endpoint را در backend خودش ایجاد کند.

POST /api/oxiranker/webhook

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

این endpoint باید فقط POST را بپذیرد، raw body را حفظ کند، timestamp و signature را بررسی کند و برای webhook.test پاسخ موفق برگرداند.

رویدادهای فعال
در نسخه فعلی، رویدادها ثابت هستند: article.created و article.updated. برای تست اتصال، مقدار رویداد webhook.test ارسال می‌شود.
مرحله 17

مقادیر .env موردنیاز برای وب‌سایت مقصد

OxiRanker به فایل‌ها یا .env وب‌سایت مقصد دسترسی ندارد. توسعه‌دهنده وب‌سایت مقصد باید این مقادیر را در backend خودش ذخیره کند.

OXIRANKER_EXPORT_API_BASE_URL=https://oxiranker.com
OXIRANKER_EXPORT_API_TOKEN=YOUR_EXPORT_TOKEN
OXIRANKER_WEBHOOK_SECRET=YOUR_WEBHOOK_SECRET
OXIRANKER_WEBHOOK_MAX_SKEW_MS=300000
OXIRANKER_SOURCE_PROVIDER=oxiranker
OXIRANKER_SOURCE_DOMAIN_IDS=1

SITE_ARTICLE_IMAGE_UPLOAD_DIR=/var/www/example.com/uploads/site-articles
SITE_ARTICLE_IMAGE_MAX_BYTES=10485760

PUBLIC_BASE_URL=https://example.com

OXIRANKER_SYNC_ENABLED=true
OXIRANKER_SYNC_HOUR=3
OXIRANKER_SYNC_MINUTE=10
OXIRANKER_SYNC_PAGE_LIMIT=20
OXIRANKER_SYNC_MAX_PAGES=50

مقادیر امنیتی

OXIRANKER_EXPORT_API_TOKEN
توکن Export API باید فقط در backend ذخیره شود.
OXIRANKER_WEBHOOK_SECRET
Webhook Secret باید فقط در backend ذخیره شود.
OXIRANKER_WEBHOOK_MAX_SKEW_MS
حداکثر اختلاف زمانی مجاز برای Webhook. مقدار 300000 یعنی ۵ دقیقه.

مقادیر ذخیره‌سازی

SITE_ARTICLE_IMAGE_UPLOAD_DIR
مسیر فیزیکی برای ذخیره تصاویر mirror شده روی سرور مقصد.
SITE_ARTICLE_IMAGE_MAX_BYTES
حداکثر اندازه مجاز تصویر برای دانلود و ذخیره‌سازی.
PUBLIC_BASE_URL
URL عمومی وب‌سایت مقصد که برای ساخت URL نهایی تصاویر و مقاله‌ها استفاده می‌شود.
امنیت Frontend
Export Token و Webhook Secret نباید داخل کد frontend، JavaScript قابل مشاهده برای کاربر، localStorage یا window object قرار بگیرند.
مرحله 18

Export API: List، JSON، HTML و ZIP

وب‌سایت مقصد می‌تواند از Export API برای دریافت لیست مقاله‌ها یا خروجی کامل هر مقاله استفاده کند.

دریافت لیست مقاله‌ها برای یک دامنه

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

limit تعداد مقاله‌ها را مشخص می‌کند و در حال حاضر بین ۱ تا ۵۰ محدود است. offset برای صفحه‌بندی استفاده می‌شود. lang اختیاری است و برای دریافت مقاله‌های یک زبان خاص مانند fa، en یا tr استفاده می‌شود.

دریافت مقاله کامل به‌صورت JSON

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

این مهم‌ترین خروجی برای وب‌سایت مقصد است. وب‌سایت مقصد باید JSON کامل مقاله را در دیتابیس خودش ذخیره کند و برای نمایش عمومی از دیتابیس خودش بخواند.

دریافت 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 برای وب‌سایت‌هایی مناسب است که خروجی آماده می‌خواهند، اما روش حرفه‌ای‌تر این است که JSON ذخیره شود و با قالب خود وب‌سایت مقصد render شود.

دریافت خروجی ZIP

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

خروجی ZIP شامل article.json، meta.json، assets.json، schema.jsonld، index.html، version.txt و README.txt است.

مرحله 19

امضای Webhook و دریافت امن Webhook

وب‌سایت مقصد باید امضای Webhook را با استفاده از raw body اصلی بررسی کند.

Headerهای Webhook

content-type: application/json
user-agent: Oxiranker-Webhooks/1.0
x-oxiranker-event: article.created
x-oxiranker-domain-id: 1
x-oxiranker-timestamp: 2026-05-20T19:35:33.000Z
x-oxiranker-signature: sha256=...

فرمول امضا

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

اگر وب‌سایت مقصد ابتدا JSON را parse کند و سپس دوباره آن را stringify کند، ترتیب یا فاصله‌ها ممکن است تغییر کنند و امضا نامعتبر شود. برای بررسی امضا باید از raw body اصلی استفاده شود.

مقایسه timing-safe
امضاها را با expected === received مقایسه نکنید. در Node.js بهتر است از crypto.timingSafeEqual استفاده کنید.
مرحله 20

نمونه کد ساده Node.js برای دریافت Webhook

این نمونه Express با raw body، timestamp، signature، webhook.test و رویدادهای مقاله کار می‌کند.

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

دیتابیس پیشنهادی وب‌سایت مقصد

وب‌سایت مقصد باید مقاله‌ها را در دیتابیس خودش ذخیره کند و با unique constraint از تکرار جلوگیری کند.

CREATE TABLE public.site_articles (
  id BIGSERIAL PRIMARY KEY,

  source_provider VARCHAR NOT NULL DEFAULT 'oxiranker',
  source_domain_id BIGINT NOT NULL,
  source_article_id BIGINT NOT NULL,
  source_request_id BIGINT NULL,

  lang VARCHAR NOT NULL,
  lang_label VARCHAR NULL,
  dir VARCHAR NOT NULL,
  is_rtl BOOLEAN NULL,

  length VARCHAR NULL,
  topic_text TEXT NULL,

  slug VARCHAR NOT NULL,
  canonical_path VARCHAR NULL,
  effective_canonical_url TEXT NULL,

  title TEXT NOT NULL,
  meta_description TEXT NULL,
  html_body TEXT NULL,

  focus_keywords JSONB NOT NULL DEFAULT '[]'::jsonb,
  category VARCHAR NULL,
  tags JSONB NOT NULL DEFAULT '[]'::jsonb,

  reading_time_minutes INTEGER NULL,

  og_title TEXT NULL,
  og_description TEXT NULL,

  cover_image_url TEXT NULL,
  og_image_url TEXT NULL,

  article_schema JSONB NOT NULL DEFAULT '{}'::jsonb,

  raw_list_item JSONB NOT NULL DEFAULT '{}'::jsonb,
  raw_detail_payload JSONB NOT NULL DEFAULT '{}'::jsonb,

  source_created_at TIMESTAMPTZ NULL,
  source_updated_at TIMESTAMPTZ NULL,

  sync_status VARCHAR NOT NULL DEFAULT 'synced',
  last_synced_at TIMESTAMPTZ NOT NULL DEFAULT now(),
  last_sync_error TEXT NULL,

  publish_status VARCHAR NOT NULL DEFAULT 'draft',
  published_at TIMESTAMPTZ NULL,
  deleted_at TIMESTAMPTZ NULL,

  created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
  updated_at TIMESTAMPTZ NOT NULL DEFAULT now(),

  source_cover_image_url TEXT NULL,
  source_og_image_url TEXT NULL,

  image_mirror_status VARCHAR NOT NULL DEFAULT 'pending',
  image_mirror_error TEXT NULL,
  image_mirrored_at TIMESTAMPTZ NULL,

  CONSTRAINT site_articles_dir_check
    CHECK (dir IN ('ltr', 'rtl')),

  CONSTRAINT site_articles_reading_time_check
    CHECK (reading_time_minutes IS NULL OR reading_time_minutes >= 0),

  CONSTRAINT site_articles_sync_status_check
    CHECK (sync_status IN ('pending', 'syncing', 'synced', 'failed')),

  CONSTRAINT site_articles_publish_status_check
    CHECK (publish_status IN ('draft', 'published', 'archived')),

  CONSTRAINT site_articles_image_mirror_status_check
    CHECK (image_mirror_status IN ('pending', 'not_needed', 'mirrored', 'failed')),

  CONSTRAINT site_articles_source_unique
    UNIQUE (source_provider, source_domain_id, source_article_id)
);
CREATE INDEX site_articles_source_lookup_idx
ON public.site_articles (source_provider, source_domain_id, source_article_id);

CREATE INDEX site_articles_source_updated_at_idx
ON public.site_articles (source_updated_at DESC);

CREATE INDEX site_articles_slug_idx
ON public.site_articles (slug)
WHERE deleted_at IS NULL;

CREATE UNIQUE INDEX site_articles_lang_slug_active_unique
ON public.site_articles (lang, slug)
WHERE deleted_at IS NULL;

CREATE INDEX site_articles_lang_publish_idx
ON public.site_articles (lang, publish_status)
WHERE deleted_at IS NULL;

CREATE INDEX site_articles_published_at_idx
ON public.site_articles (published_at DESC)
WHERE deleted_at IS NULL;

CREATE INDEX site_articles_focus_keywords_gin_idx
ON public.site_articles USING gin (focus_keywords);

CREATE INDEX site_articles_tags_gin_idx
ON public.site_articles USING gin (tags);

CREATE INDEX site_articles_article_schema_gin_idx
ON public.site_articles USING gin (article_schema);
نکته PostgreSQL
در PostgreSQL، برای متن فارسی یا Unicode از کوتیشن ساده استفاده می‌شود. چیزی مثل N'...' لازم نیست و نباید استفاده شود.
مرحله 22

جلوگیری از تکرار با UPSERT

مقاله نباید برای هر Webhook دوباره insert شود. اگر از قبل وجود دارد، باید به‌روزرسانی شود.

کلید اصلی برای جلوگیری از تکرار
مهم‌ترین 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();
مرحله 23

دریافت مقاله کامل و sync بعد از Webhook

وقتی یک Webhook می‌رسد، وب‌سایت مقصد باید مقاله کامل را از Export API دریافت کند، تصاویر را mirror کند و مقاله را 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 و قوانین ذخیره‌سازی تصویر

وب‌سایت مقصد باید تصاویر را دانلود کند، روی زیرساخت خودش ذخیره کند و URLهای مقاله را جایگزین کند.

قوانین Image Mirroring

فقط URLهای HTTPS باید پذیرفته شوند.
فایل باید واقعاً تصویر باشد.
content-type باید با image/ شروع شود.
فایل‌های خالی نباید ذخیره شوند.
اندازه فایل نباید از محدودیت بیشتر شود.
اگر cover و og یکسان هستند، فقط یک بار دانلود کنید.
بعد از mirroring، URLها را داخل html_body و article_schema جایگزین کنید.

مسیر ذخیره‌سازی پیشنهادی

/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

Sync روزانه برای اطمینان

Webhook به‌تنهایی کافی نیست. وب‌سایت مقصد باید sync روزانه هم داشته باشد تا مقاله‌های ازدست‌رفته را بازیابی کند.

چرا sync روزانه لازم است؟

وب‌سایت مقصد ممکن است موقتاً از دسترس خارج شود.
Webhook ممکن است timeout شود.
ممکن است مشکلات deployment یا شبکه رخ دهد.
ممکن است مقاله تولید شود اما Webhook آن از دست برود.

الگوریتم sync روزانه

offset را از 0 شروع کنید.
از limit 20 یا 50 استفاده کنید.
لیست مقاله‌ها را از Export List API دریافت کنید.
برای هر آیتم، Export JSON همان مقاله را دریافت کنید.
تصاویر را mirror کنید.
مقاله را با UPSERT ذخیره کنید.
offset را افزایش دهید و تا تکمیل total ادامه دهید.
مرحله 26

نمایش مقاله‌ها در وب‌سایت مقصد

بعد از ذخیره مقاله‌ها در site_articles، وب‌سایت مقصد نباید برای هر نمایش عمومی به OxiRanker متصل شود. باید از دیتابیس خودش بخواند.

صفحه لیست بلاگ

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;

صفحه جزئیات مقاله

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;
URL پیشنهادی مقاله
برای وب‌سایت‌های چندزبانه، می‌توانید از الگوی /{lang}/blog/{slug} استفاده کنید. مثال: /fa/blog/smart-internal-links
مرحله 27

SEO، Schema، RTL و HTML rendering

وب‌سایت مقصد باید داده‌های SEO را از دیتابیس خودش بخواند و آن را در صفحه مقاله render کند.

فیلدهای SEO

title برای عنوان صفحه
meta_description برای meta description
effective_canonical_url برای canonical
og_title و og_description برای Open Graph
og_image_url یا cover_image_url برای تصویر پیش‌نمایش اجتماعی
article_schema برای JSON-LD

HTML rendering

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

اگر وب‌سایت مقصد سیاست امنیتی سخت‌گیرانه دارد، می‌تواند HTML را قبل از ذخیره یا قبل از نمایش sanitize کند.

RTL و LTR

هر مقاله فیلدهای lang، dir و is_rtl دارد. اگر dir برابر rtl باشد، صفحه مقاله باید راست‌به‌چپ render شود.

<article dir="rtl">
  ...
</article>
مرحله 28

خطاهای رایج و معنی آن‌ها

خطاهای وب‌سایت مقصد باید انگلیسی، واضح و قابل‌فهم باشند تا عیب‌یابی سریع انجام شود.

خطاهای Export API

توکن export دامنه وجود ندارد.
توکن Export API ارسال نشده است. باید Authorization: Bearer YOUR_EXPORT_TOKEN ارسال شود.
توکن export دامنه نامعتبر است.
توکن اشتباه است، چرخانده شده، مربوط به این دامنه نیست یا domainId اشتباه است.
توکن scope موردنیاز را ندارد.
توکن scope موردنیاز را ندارد. توکن‌های جدید باید articles:read و exports:read داشته باشند.
lang نامعتبر است.
مقدار lang در query parameter به‌درستی ارسال نشده است.

خطاهای Webhook

URL وبهوک باید از HTTPS استفاده کند.
URL وبهوک باید با https:// شروع شود.
امضای Webhook نامعتبر است.
Webhook Secret اشتباه است، secret چرخانده شده، raw body تغییر کرده یا امضا به‌درستی محاسبه نشده است.
timestamp وبهوک منقضی شده یا نامعتبر است.
timestamp قدیمی است، در آینده است یا نامعتبر است. ساعت سرور و NTP را بررسی کنید.
ارسال تست Webhook ناموفق بود.
وب‌سایت مقصد وضعیت موفق 200 تا 299 برنگرداند، timeout شد یا webhook.test را به‌درستی مدیریت نکرد.
مرحله 29

چک‌لیست نهایی تست API و Webhook

بعد از پیاده‌سازی، این تست‌ها اتصال را از ابتدا تا انتها تأیید می‌کنند.

تست Export Token و JSON

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

اگر Webhook مقصد را با Postman بدون امضا صدا بزنید، باید خطای امنیتی دریافت کنید. این یعنی endpoint فعال است و امنیت را بررسی می‌کند.

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

سپس داخل OxiRanker روی Test Webhook کلیک کنید. اگر receiver از webhook.test پشتیبانی کند، response_status باید 200 باشد.

بعد از چرخش
اگر Webhook Secret یا Export Token چرخانده شد، مقدار جدید را فوراً کپی کنید، آن را در .env وب‌سایت مقصد جایگزین کنید و backend مقصد را restart یا redeploy کنید.
مرحله 30

قوانین مهم برای پیاده‌سازی صحیح

این قوانین باید در یکپارچه‌سازی API رعایت شوند تا سیستم امن، پایدار و قابل نگهداری بماند.

Export Token باید فقط در backend ذخیره شود.
Webhook Secret باید فقط در backend ذخیره شود.
Endpoint وبهوک مقصد باید دقیقاً /api/oxiranker/webhook باشد.
URL وبهوک باید از HTTPS استفاده کند.
امضای Webhook و timestamp همیشه باید بررسی شوند.
مقاله‌ها باید با UPSERT ذخیره شوند، نه insert ساده.
برای جلوگیری از تکرار، unique constraint لازم است.
تصاویر باید از OxiRanker دانلود و روی وب‌سایت مقصد ذخیره شوند.
وب‌سایت مقصد نباید به‌صورت دائمی به URLهای تصاویر OxiRanker وابسته باشد.
URLهای تصاویر باید داخل html_body و article_schema جایگزین شوند.
اگر Webhook شکست خورد، sync روزانه باید مقاله ازدست‌رفته را بازیابی کند.
خطاهایی که به کاربران یا در logها نمایش داده می‌شوند باید انگلیسی و قابل‌فهم باشند.
اگر یک مقاله archived باشد، sync خودکار نباید دوباره آن را منتشر کند.
raw_detail_payload و raw_list_item باید حفظ شوند تا هیچ داده‌ای از خروجی اصلی از دست نرود.
اگر Export Token یا Webhook Secret چرخانده شد، مقدار جدید باید در .env جایگزین شود و backend باید restart یا redeploy شود.
مرحله 31

خلاصه جریان کامل API

بعد از تکمیل این مراحل، وب‌سایت سفارشی شما می‌تواند مقاله‌های تولیدشده در OxiRanker را به‌صورت امن دریافت، ذخیره، mirror و منتشر کند.

ثبت‌نام در OxiRanker
افزودن دامنه
تأیید مالکیت دامنه
انتخاب زبان‌ها و افزودن Blog URL
افزودن کلمات کلیدی برای هر زبان
تکمیل Article Profile
تنظیم Telegram در صورت نیاز
تنظیم موتور خودکار مقاله
ایجاد اولین مقاله از Generate
بررسی Preview و SEO Report
دریافت Export Token از API Output
دریافت endpointهای آماده Export API
دریافت Webhook Secret
ایجاد endpoint ثابت /api/oxiranker/webhook در backend مقصد
ذخیره Token و Secret در .env وب‌سایت مقصد
ایجاد جدول site_articles و unique constraint
پیاده‌سازی دریافت مقاله کامل از Export JSON
پیاده‌سازی Image Mirroring
پیاده‌سازی UPSERT
پیاده‌سازی sync روزانه
Render مقاله، SEO، OG و Schema از دیتابیس مقصد
اجرای تست‌های نهایی Export API و Webhook