سایت خودتان را به نیوارا وصل کنید
با پیادهسازی سه endpoint ساده، هر سایت یا CMS اختصاصی میتواند محتوای تولیدشده در نیوارا را بهصورت خودکار دریافت، منتشر و بهروزرسانی کند: متن، سئو، دستهبندی، برچسب و تصاویر. سایتهای وردپرسی فقط کافی است پلاگین آماده را نصب کنند.
- آدرس پایه نیوارا
- https://studio.manova.ir
- قالب داده
- JSON با UTF-8
- احراز هویت
- HMAC-SHA256 با کلید جدا برای هر سایت
شروع سریع
- در پنل نیوارا، صفحه «سایتها»، سایت را با پلتفرم CMS اختصاصی و دامنه دقیق آن اضافه کنید و کد اتصال (
NV-XXXXXXXX) را بردارید. - سه endpoint زیر را روی سایت پیاده کنید، زیر یک آدرس پایه مثل
https://example.com/api/nivara:GET /statusPOST /publishPOST /disconnect
- در بخش مدیریت CMS، کد اتصال را بگیرید و درخواست اتصال و سپس تأیید را بفرستید.
- از این به بعد همه درخواستهای نیوارا را با بررسی امضا بپذیرید.
۱. اتصال
POSThttps://studio.manova.ir/api/sites/connect
سایت (از سمت سرور، نه مرورگر) کد اتصال را میفرستد و یک کلید مخفی اختصاصی دریافت میکند.
| فیلد | نوع | توضیح |
|---|---|---|
code | string | کد اتصال از پنل نیوارا، به شکل NV- و ۸ کاراکتر هگز. |
siteUrl | URL | آدرس اصلی سایت. دامنه (و پورت) باید دقیقاً همان دامنه ثبتشده در نیوارا باشد؛ www نادیده گرفته میشود. |
endpoint | URL | آدرس پایه سه endpoint شما. باید روی همان دامنه یا یک زیردامنه آن باشد. |
platform | "custom" | "wordpress" | باید با پلتفرمی که سایت در پنل با آن ثبت شده یکی باشد. |
software, version, siteName | string (اختیاری) | برای نمایش در پنل. |
curl -X POST https://studio.manova.ir/api/sites/connect \
-H "Content-Type: application/json" \
-d '{
"code": "NV-1A2B3C4D",
"siteUrl": "https://example.com",
"endpoint": "https://example.com/api/nivara",
"platform": "custom",
"software": "my-cms",
"version": "2.3",
"siteName": "My Site"
}'
# پاسخ موفق:
# {"ok":true,"siteId":"6f1c…","secret":"q8Zr…","verifyUrl":"https://studio.manova.ir/api/sites/verify"}
curl -X POST https://studio.manova.ir/api/sites/verify \
-H "Content-Type: application/json" \
-d '{"siteId":"6f1c…"}'
# {"ok":true}پاسخ موفق شامل siteId و secret است. هر دو را امن ذخیره کنید؛ کلید فقط همین یک بار برگردانده میشود. اتصال دوباره با همان کد، کلید جدید میسازد و کلید قبلی از کار میافتد.
۲. تأیید مالکیت
POSThttps://studio.manova.ir/api/sites/verify
بلافاصله بعد از ذخیره کلید، این درخواست را با بدنه {"siteId": "…"} بفرستید. نیوارا در همان لحظه یک درخواست امضاشده به GET {endpoint}/status میزند. سایت فقط وقتی «متصل» میشود که این درخواست با موفقیت جواب بگیرد؛ بنابراین کسی که فقط کد اتصال را دارد نمیتواند سرور دیگری را جای سایت شما وصل کند.
پاسخ: {"ok": true} یا {"ok": false, "error": "…"} با کد ۴۰۰.
امضای درخواستها
هر درخواستی که نیوارا به سایت میفرستد این هدرها را دارد:
| هدر | مقدار |
|---|---|
X-Nivara-Site | همان siteId |
X-Nivara-Timestamp | زمان یونیکس به ثانیه |
X-Nivara-Signature | امضای هگز (۶۴ کاراکتر) |
X-Nivara-Protocol | 1 |
امضا اینطور ساخته میشود:
signature = hex( HMAC-SHA256( secret, "{timestamp}.{METHOD}.{path}.{body}" ) )METHOD: با حروف بزرگ، مثلGETیاPOST.path: مسیر نسبت به endpoint، یعنی/status،/publishیا/disconnect.body: متن خام بدنه درخواست، دقیقاً همان بایتها (برای GET رشته خالی). امضا را قبل از تبدیل JSON بررسی کنید.
سایت باید درخواست را رد کند (کد ۴۰۱) اگر: X-Nivara-Site متفاوت باشد، اختلاف زمان بیش از ۳۰۰ ثانیه باشد، امضا درست نباشد (مقایسه با زمان ثابت)، یا همین امضا در ۱۰ دقیقه اخیر دیده شده باشد.
import { createHmac, timingSafeEqual } from "node:crypto";
const MAX_SKEW = 300; // seconds
const seen = new Map(); // signature -> expiry (use Redis/DB if you run several servers)
/**
* req: { method, path, headers, rawBody }
* path = path relative to your endpoint, e.g. "/publish"
* rawBody = the exact request body string ("" for GET). Verify BEFORE JSON.parse.
*/
export function verifyNivara(req, { siteId, secret }) {
const ts = Number(req.headers["x-nivara-timestamp"]);
const sig = String(req.headers["x-nivara-signature"] || "");
if (req.headers["x-nivara-site"] !== siteId) return "wrong site";
if (!Number.isFinite(ts) || Math.abs(Date.now() / 1000 - ts) > MAX_SKEW) return "expired";
const expected = createHmac("sha256", secret)
.update(`${ts}.${req.method}.${req.path}.${req.method === "GET" ? "" : req.rawBody}`)
.digest("hex");
const a = Buffer.from(expected), b = Buffer.from(sig);
if (a.length !== b.length || !timingSafeEqual(a, b)) return "bad signature";
const now = Date.now();
for (const [k, exp] of seen) if (exp < now) seen.delete(k);
if (seen.has(sig)) return "replayed";
seen.set(sig, now + 2 * MAX_SKEW * 1000);
return null; // OK
}GET /status
بررسی سلامت اتصال. هنگام تأیید و هر بار که کاربر در پنل «بررسی اتصال» بزند صدا زده میشود.
| فیلد پاسخ | الزامی | توضیح |
|---|---|---|
ok | بله | باید true باشد. |
postCount | خیر | تعداد پستهای منتشرشده؛ در پنل نمایش داده میشود. |
software, version, siteName | خیر | برای نمایش در پنل. |
seo | خیر | نام افزونه سئو یا null. |
HTTP/1.1 200 OK
Content-Type: application/json
{
"ok": true,
"software": "my-cms",
"version": "2.3",
"siteName": "My Site",
"postCount": 128,
"seo": null
}POST /publish
ایجاد یا بهروزرسانی یک پست. باید idempotent باشد: اولین بار که یک jobId میرسد پست بسازید و دفعات بعد (ویرایش، تولید دوباره، تبدیل پیشنویس به انتشار) همان پست را بهروز کنید.
| فیلد | توضیح |
|---|---|
jobId | شناسه سفارش در نیوارا؛ کلید یکتای پست در سایت شما. |
mode | "publish" (انتشار عمومی) یا "draft" (پیشنویس برای بازبینی). |
title, slug, excerpt | عنوان، اسلاگ انگلیسی و خلاصه. |
html | بدنه آماده با سرتیترها، لیستها، جدولها، تصاویر داخل متن، بخش پرسشهای متداول و اسکیمای FAQ. شامل h1 نیست. |
seo | metaTitle، metaDescription، focusKeyword و secondaryKeywords. |
category, tags | نام دستهبندی و برچسبها. اگر options.createCategories برابر false است دسته جدید نسازید. |
featuredImage | { url, alt }؛ url ممکن است null باشد. تصویر را دانلود و در رسانه سایت ذخیره کنید. |
inlineImages | تصاویر داخل متن: [{ url, alt, caption }]. همین آدرسها در html هم آمدهاند؛ میتوانید دانلود و جایگزین کنید. |
faqSchema | اسکیمای FAQPage برای JSON-LD (در html هم درج شده). |
document | سند ساختاریافته کامل (پایینتر)، اگر میخواهید خودتان HTML بسازید. |
POST /api/nivara/publish HTTP/1.1
Host: example.com
Content-Type: application/json
X-Nivara-Site: 6f1c2a4e-…
X-Nivara-Timestamp: 1791990000
X-Nivara-Signature: 9b1f0c…e4
X-Nivara-Protocol: 1
{
"protocol": 1,
"jobId": "a3d9b8c2-…",
"mode": "publish",
"title": "هاست وردپرس با هاست معمولی چه فرقی دارد؟",
"slug": "wordpress-hosting-vs-shared-hosting",
"excerpt": "…",
"html": "<p>…</p><h2>…</h2>…",
"seo": {
"metaTitle": "…",
"metaDescription": "…",
"focusKeyword": "هاست وردپرس",
"secondaryKeywords": ["…", "…"]
},
"category": "هاستینگ",
"tags": ["وردپرس", "هاست"],
"featuredImage": { "url": "https://studio.manova.ir/media/a3d9…/featured-1a2b3c.webp", "alt": "…" },
"inlineImages": [{ "url": "https://studio.manova.ir/media/a3d9…/image-6-4d5e6f.webp", "alt": "…", "caption": "…" }],
"faqSchema": { "@context": "https://schema.org", "@type": "FAQPage", "mainEntity": [] },
"options": { "createCategories": true },
"document": { "title": "…", "blocks": [], "faq": [], "internalLinkIdeas": [] }
}| فیلد پاسخ | الزامی | توضیح |
|---|---|---|
postId | بله | شناسه پست در سایت شما. |
url | خیر | نشانی پست؛ در پنل به کاربر نشان داده میشود. |
status, postCount | خیر | وضعیت پست و تعداد کل پستهای منتشرشده. |
warnings | خیر | آرایهای از پیامهای غیربحرانی (مثلاً «تصویر دانلود نشد»)؛ به کاربر نمایش داده میشود. |
POST /disconnect
وقتی کاربر اتصال را در پنل قطع میکند فرستاده میشود (بدنه {}). siteId و secret را پاک کنید و {"ok": true} برگردانید. پنل کد اتصال جدیدی برای اتصال دوباره میسازد.
ساختار محتوا
فیلد document نسخه کامل و مستقل از پلتفرم محتواست: title، slug، metaTitle، metaDescription، focusKeyword، secondaryKeywords، excerpt، category، tags، featuredImage، blocks، faq و internalLinkIdeas. بدنه، آرایهای از بلوکهاست:
{
"type": "heading", // heading | paragraph | list | quote | table | image
"level": 2, // headings only: 2 or 3
"text": "…", // heading, paragraph, quote (paragraphs may contain **bold**)
"ordered": null, // lists only
"items": null, // lists only: ["…", "…"]
"headers": null, // tables only: ["…", "…"]
"rows": null, // tables only: [["…", "…"], …]
"alt": null, // images only: Persian alt text
"caption": null, // images only
"imagePrompt": null // images only: English description used to generate the image
}زمانبندی و تلاش دوباره
- محتوای بدون زمانبندی بلافاصله بعد از تولید فرستاده میشود: اگر کاربر «انتشار خودکار» را روشن کرده باشد با
mode: "publish"و در غیر این صورت"draft". - محتوای زمانبندیشده سر موعد با
"publish"فرستاده میشود (دقت چند ثانیه). - اگر سایت خطا برگرداند یا در ۲۵ ثانیه پاسخ ندهد، نیوارا برای محتوای زمانبندیشده تا ۵ بار با فاصله دو دقیقه دوباره تلاش میکند. ارسالهای دستی کاربر فوراً تکرار میشوند.
- ریدایرکت دنبال نمیشود؛ endpoint را با آدرس نهایی (مثلاً https و بدون www اضافه) ثبت کنید.
خطاها
برای رد کردن درخواست، کد HTTP غیر 2xx و یک پیام برگردانید؛ متن message (یا error) عیناً به کاربر نیوارا نشان داده میشود، پس آن را خوانا بنویسید.
HTTP/1.1 401 Unauthorized
Content-Type: application/json
{ "message": "bad signature" }| خطای اتصال | علت |
|---|---|
| کد اتصال پیدا نشد | کد اشتباه است یا سایت در پنل حذف شده. |
| این کد برای … است، اما درخواست از … آمده | siteUrl با دامنه ثبتشده یکی نیست. |
| آدرس API باید روی همان دامنه سایت باشد | endpoint روی دامنه دیگری است. |
| سایت پاسخ معتبری به بررسی اتصال نداد | GET /status خطا داد، امضا را رد کرد یا ok: true برنگرداند. |
نمونه کامل
یک گیرنده ساده که هر سه endpoint را پیاده میکند. نسخه اجرایی کامل (بدون وابستگی) را از اینجا دانلود کنید.
// Minimal receiver (Node.js, no dependencies). Full version: https://studio.manova.ir/downloads/custom-cms-receiver.mjs
import { createServer } from "node:http";
import { verifyNivara } from "./verify-nivara.js"; // the function from the "request signing" section
const BASE = "/api/nivara";
const config = await loadSetting("nivara"); // { siteId, secret } saved while connecting
createServer((req, res) => {
let rawBody = "";
req.on("data", c => (rawBody += c));
req.on("end", async () => {
const send = (status, json) => { res.writeHead(status, { "Content-Type": "application/json" }); res.end(JSON.stringify(json)); };
const url = new URL(req.url, "http://x");
if (!url.pathname.startsWith(BASE)) return send(404, { message: "not found" });
const path = url.pathname.slice(BASE.length); // "/status" | "/publish" | "/disconnect"
const problem = verifyNivara({ method: req.method, path, headers: req.headers, rawBody }, config);
if (problem) return send(401, { message: problem });
if (req.method === "GET" && path === "/status")
return send(200, { ok: true, software: "my-cms", version: "2.3", siteName: "My Site", postCount: await countPublished() });
if (req.method === "POST" && path === "/publish") {
const p = JSON.parse(rawBody);
// Upsert by jobId: the same order always updates the same post.
const post = await upsertPostByNivaraJob(p.jobId, {
title: p.title, slug: p.slug, html: p.html, excerpt: p.excerpt,
status: p.mode === "publish" ? "published" : "draft",
metaTitle: p.seo.metaTitle, metaDescription: p.seo.metaDescription,
category: p.category, tags: p.tags,
featuredImage: p.featuredImage.url && await importImage(p.featuredImage.url, p.featuredImage.alt),
});
return send(200, { ok: true, postId: String(post.id), url: post.url, status: p.mode, postCount: await countPublished() });
}
if (req.method === "POST" && path === "/disconnect") {
await deleteSetting("nivara");
return send(200, { ok: true });
}
send(404, { message: "not found" });
});
}).listen(8080);وردپرس
برای وردپرس نیازی به برنامهنویسی نیست. پلاگین نیوارا را نصب کنید، در «تنظیمات، نیوارا» آدرس https://studio.manova.ir و کد اتصال را وارد کنید. پلاگین همین پروتکل را پیاده میکند و پست، دستهبندی، برچسب، تصویر شاخص و تصاویر داخل متن (در کتابخانه رسانه)، و فیلدهای Yoast یا Rank Math را تنظیم میکند.
/wp-json/) باید از اینترنت در دسترس باشد. اگر افزونه امنیتی درخواستهای ناشناس REST را میبندد، مسیر nivara/v1 را مجاز کنید.سفارش محتوا از داخل CMS
علاوه بر دریافت محتوا، CMS متصل میتواند خودش در نیوارا سفارش ثبت کند: موجودی و قیمت را ببیند، سفارش بدهد، وضعیت را دنبال کند، دوباره تولید کند، تصویر بسازد، منتشر کند و کاربر را برای شارژ کیف پول به درگاه بفرستد. همه هزینهها از کیف پول صاحب سایت در نیوارا کم میشود؛ CMS هیچ دسترسی مستقیمی به پول ندارد.
- این قابلیت بهصورت پیشفرض خاموش است. صاحب حساب باید در پنل نیوارا، کارت سایت، گزینه «سفارش محتوا از خود CMS» را روشن کند.
- صاحب حساب میتواند سقف هزینه ماهانه (ماه شمسی) بگذارد؛ بدون سقف، محدودیتی جز موجودی کیف پول نیست.
- محتوای سفارشها مثل قبل از طریق POST /publish خود CMS تحویل داده میشود؛ پیگیری وضعیت اختیاری است.
امضای درخواست به نیوارا
همان siteId و secret اتصال و همان روش امضا، این بار در جهت برعکس. هر درخواست به https://studio.manova.ir/api/v1 باید هدرهای X-Nivara-Site، X-Nivara-Timestamp و X-Nivara-Signature را داشته باشد.
signature = hex( HMAC-SHA256( secret, "{timestamp}.{METHOD}.{path}.{body}" ) )path: مسیر بعد از/api/v1بههمراه query string، مثلاً/jobs?status=draft&limit=20.body: دقیقاً همان رشته JSON که میفرستید (برای GET رشته خالی). اول رشته را بسازید، امضا کنید، بعد همان را بفرستید.- زمان باید حداکثر ۵ دقیقه با ساعت نیوارا فاصله داشته باشد.
- درخواستهای تغییردهنده (POST، بهجز
/quote) با امضای تکراری اجرا نمیشوند و409 duplicate_requestمیگیرند؛ این از ثبت دوباره سفارش با دوبار کلیک جلوگیری میکند. درخواستهای GET از این قاعده مستثنیاند. - سایت باید «متصل» باشد؛ بعد از قطع اتصال یا اتصال دوباره، کلید قبلی کار نمیکند.
import { createHmac } from "node:crypto";
const NIVARA = "https://studio.manova.ir/api/v1";
const { siteId, secret } = await loadSetting("nivara"); // saved while connecting
/** Signed request to Nivara. path includes the query string, e.g. "/jobs?status=draft". */
export async function nivara(method, path, body) {
const raw = body === undefined ? "" : JSON.stringify(body);
const ts = Math.floor(Date.now() / 1000);
const signature = createHmac("sha256", secret)
.update(`${ts}.${method}.${path}.${method === "GET" ? "" : raw}`)
.digest("hex");
const res = await fetch(NIVARA + path, {
method,
headers: { "Content-Type": "application/json", "X-Nivara-Site": siteId, "X-Nivara-Timestamp": String(ts), "X-Nivara-Signature": signature },
body: method === "GET" ? undefined : raw,
});
const json = await res.json();
if (!json.ok) throw Object.assign(new Error(json.error.message), { code: json.error.code, status: res.status });
return json.data;
}
// Examples
const account = await nivara("GET", "/account");
const { job } = await nivara("POST", "/jobs", { type: "article", title: "راهنمای انتخاب هاست", keyword: "هاست وردپرس" });
const detail = await nivara("GET", `/jobs/${job.id}`);همه پاسخها قالب ثابت دارند: موفق { "ok": true, "data": … } و ناموفق { "ok": false, "error": { "code", "message" } }. متن message فارسی است و میتوانید مستقیم به کاربر نشان دهید.
endpointهای سفارش
| endpoint | کار |
|---|---|
GET /account | موجودی، قیمتها، فعال بودن سفارش و تصویر، سقف و هزینه این ماه، امکان شارژ. |
POST /quote | قیمت سفارش قبل از ثبت: { type, autoImage } ← { cost, enoughBalance, withinMonthlyLimit, canOrder }. |
POST /jobs | ثبت سفارش (کسر هزینه). پاسخ ۲۰۱ با job در وضعیت working. |
GET /jobs | فهرست سفارشهای همین سایت: ?status=&limit=(۱ تا ۱۰۰)&before=(createdAt برای صفحه بعد). |
GET /jobs/{id} | یک سفارش بههمراه document و آدرس تصاویر (وقتی آماده است). |
POST /jobs/{id}/regenerate | تولید دوباره با { notes } اختیاری. اولین بار رایگان، بعد از آن به قیمت سفارش. |
POST /jobs/{id}/image | ساخت یا ساخت دوباره تصویر شاخص با { prompt } اختیاری. همزمان؛ تا دو دقیقه طول میکشد. |
POST /jobs/{id}/publish | ارسال فوری به سایت با { mode: "publish" | "draft" }. نیوارا در همین لحظه POST /publish شما را صدا میزند. |
POST /topup | شروع شارژ کیف پول: { amount, returnUrl } ← { payUrl, paymentId }. |
POST /jobs/{id}/publish در حین پاسخدادن، endpoint انتشار خود CMS را صدا میزند، سرور CMS باید بتواند همزمان چند درخواست را پاسخ دهد.ثبت و پیگیری سفارش
POST /api/v1/jobs
{
"type": "article", // article | product | landing | rewrite
"title": "راهنمای انتخاب هاست وردپرس", // title یا keyword (حداقل یکی)
"keyword": "هاست وردپرس",
"audience": "صاحبان فروشگاههای آنلاین",
"notes": "به پشتیبانی فارسی اشاره شود",
"tone": "professional", // professional | friendly | expert | sales
"autoImage": true, // فقط اگر تولید تصویر فعال باشد هزینه دارد
"autoSeo": true,
"internalLinks": true,
"autoPublish": false, // false: پیشنویس به سایت فرستاده میشود
"scheduledAt": null // یا "2026-11-01T09:00:00+03:30"
}| status | معنی |
|---|---|
working | در صف تولید. |
generating | در حال نوشتن متن و ساخت تصویر. |
draft | آماده؛ اگر سایت متصل باشد بهصورت پیشنویس (یا منتشرشده، با autoPublish) فرستاده شده است. |
scheduled | آماده و منتظر زمان انتشار. |
done | در سایت منتشر شده است. |
failed | تولید ناموفق بود؛ هزینه کامل به کیف پول برگشته است. علت در error. |
برای پیگیری، هر چند ثانیه GET /jobs/{id} را بخوانید یا منتظر رسیدن محتوا به POST /publish خودتان بمانید. فیلد publish وضعیت تحویل به سایت و image.featuredUrl آدرس تصویر شاخص است.
شارژ کیف پول
POST /topup با { "amount": 500000, "returnUrl": "https://example.com/admin/nivara" } (تومان) یک پرداخت زرینپال میسازد. کاربر را به payUrl بفرستید؛ بعد از پرداخت، صفحه نتیجه نیوارا (بدون نیاز به ورود) او را به returnUrl برمیگرداند با:
?nivara_payment=paid|canceled|failed|pending&payment_id=…
returnUrl باید روی دامنه همین سایت باشد. مبلغ باید بین حداقل و حداکثر اعلامشده در GET /account (بخش topUp) باشد. برای اطمینان، بعد از بازگشت موجودی را دوباره از GET /account بخوانید.
کدهای خطا
HTTP/1.1 402 Payment Required
{ "ok": false, "error": { "code": "insufficient_balance", "message": "موجودی کیف پول کافی نیست." } }| code | HTTP | معنی |
|---|---|---|
unauthorized | 401 | امضا، زمان یا هدرها نامعتبر است یا سایت متصل نیست. |
duplicate_request | 409 | همین درخواست POST (با همین امضا) قبلاً اجرا شده است. |
forbidden | 403 | حساب صاحب سایت غیرفعال است. |
orders_disabled | 403 | سفارش از CMS در پنل نیوارا خاموش است. |
invalid_request | 400 | بدنه یا پارامترها نامعتبر است؛ message دلیل را میگوید. |
insufficient_balance | 402 | موجودی کیف پول کافی نیست؛ کاربر را به شارژ هدایت کنید. |
monthly_limit_reached | 402 | سقف هزینه ماهانه این سایت پر شده است. |
email_unverified | 403 | صاحب حساب هنوز ایمیلش را تأیید نکرده؛ سفارش و شارژ تا تأیید ممکن نیست. |
not_found | 404 | سفارش وجود ندارد یا متعلق به این سایت نیست. |
not_ready | 409 | محتوا هنوز آماده نیست (برای انتشار، تولید دوباره یا تصویر). |
images_unavailable | 503 | تولید تصویر در نیوارا فعال نیست. |
image_failed | 502 | ساخت تصویر ناموفق بود؛ هزینه آن برگشت داده شده. |
publish_failed | 502 | ارسال به سایت ناموفق بود (endpoint انتشار شما خطا داد یا در دسترس نبود). |
payment_unavailable | 503 | درگاه پرداخت فعال نیست یا مبلغ خارج از بازه مجاز است. |
rate_limited | 429 | بیش از ۱۲۰ درخواست در دقیقه یا ۲۰ سفارش در دقیقه. |
internal_error | 500 | خطای داخلی؛ کمی بعد دوباره تلاش کنید. |
چکلیست امنیتی
- کلید مخفی را رمزنگاریشده یا در محل امن ذخیره کنید و هرگز در کد سمت مرورگر نگذارید.
- امضا را روی بدنه خام و با مقایسه زمانثابت بررسی کنید.
- درخواستهای قدیمیتر از ۵ دقیقه و امضاهای تکراری را رد کنید.
- endpoint را فقط روی HTTPS در دسترس بگذارید.
- HTML دریافتی از نیوارا امضاشده و قابل اعتماد است، اما اگر CMS شما محتوای کاربران دیگر را هم نمایش میدهد، سیاست پاکسازی خودتان را اعمال کنید.
- تصاویر را دانلود و روی سرور خودتان ذخیره کنید؛ به لینکهای نیوارا بهعنوان میزبان دائمی تکیه نکنید.
نسخه متنی این مستندات: publish-protocol.md