Skip to content

المهام وخطافات الويب ​

توليد الوسائط (الفيديو والصور والموسيقى) غير متزامن: تنشئ مهمة (job)، ثم تسترجع النتيجة عند جاهزيتها. هناك طريقتان لمعرفة اكتمال المهمة: الاستطلاع (polling) وخطاف الويب (push). فيما يلي شرح لبنية المهمة وكيفية استرجاع النتيجة بموثوقية.

دورة حياة المهمة ​

POST /media/generate  →  status: processing  ──►  done   (result_url جاهز)
        │                                     └─►  failed (error، وتُعاد الأموال)
        └── (اختياري) callback_url → خطاف ويب فوري عند done/failed
  1. الإنشاء. أرسل POST /media/generate مع النموذج والمعلمات، وسيُرجع id ورابطًا للاستطلاع:
json
{ "id": "beefb531-…", "model": "image/nano-banana-2", "status": "processing",
  "poll": "https://nordrouter.com/media/job/beefb531-…" }
  1. الانتظار. تنتقل المهمة من processing إلى done (أو failed). يستغرق توليد الصور ثوانٍ، بينما قد يستغرق توليد الفيديو والموسيقى من عشرات الثواني إلى بضع دقائق.

  2. النتيجة. عند اكتمال المهمة، تكون قيمة result_url مملوءة (رابط إلى nordrouter.com)؛ يُحتفظ بالملف لفترة محدودة (retention_days من GET /media/models) — نزّله للاحتفاظ به.

تنسيق استجابة GET /media/job/:id (وكذلك جسم طلب خطاف الويب، انظر أدناه) موحّد:

json
{ "id": "beefb531-…", "status": "done",
  "result_url": "https://nordrouter.com/media/file/beefb531-…",
  "cost_usd": 0.052, "error": null }
  • status — إحدى القيم processing أو done أو failed.
  • result_url — تكون قيمته مملوءة فقط عند done، وإلا فهي null.
  • result_url_2 — المسار الثاني في نماذج الموسيقى (Suno)، وإلا فهو null.
  • results — جميع ملفات المهمة: [{ "index": 1, "name": "Результат 1", "ext": "png", "url": "…" }] (في مهام 3D: النموذج والمعاينة، وفي فصل المسارات: مسارات «الغناء» و«الموسيقى الآلية» وغيرها).
  • cost_usd — التكلفة الفعلية (تُخصم حسب الاستخدام؛ لا تُحتسب تكلفة عمليات التوليد الفاشلة، ويُعاد المبلغ المحجوز).
  • error — نص الخطأ عند failed، وإلا فهو null.

الطريقة 1. الاستطلاع (polling) ​

أبسط طريقة هي طلب GET /media/job/:id دوريًا إلى أن تصبح قيمة status هي done أو failed:

bash
curl https://nordrouter.com/media/job/beefb531-…

أجرِ الاستطلاع كل 2–3 ثوانٍ. يعمل الاستطلاع دائمًا ولا يتطلب عنوانًا عامًا، لذا فهو مناسب للبرامج النصية ودفاتر الملاحظات وCI.

الطريقة 2. Webhooks (الدفع) ​

لتجنّب الاستعلام المتكرر، أرسل callback_url إلى POST /media/generate، وسنرسل طلب POST إلى عنوانك عند اكتمال المهمة:

bash
curl https://nordrouter.com/media/generate \
  -H "Authorization: Bearer sk-nr-YOUR-KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "image/nano-banana-2",
    "input": { "prompt": "…" },
    "callback_url": "https://your-server.com/webhooks/nordrouter",
    "callback_secret": "любая-ваша-строка"
  }'

إذا قُبل callback_url، فستتضمن الاستجابة عند إنشاء المهمة "webhook": "registered". يُتحقَّق من callback_url فورًا عند إنشاء المهمة: إذا لم يكن صالحًا (ليس https أو كان عنوانًا خاصًا/محليًا)، فسيعيد الطلب 400 مع السبب ({ "error": "callback_url отклонён: …", "code": "invalid_callback" })، ولن تُنشأ المهمة.

محتوى Webhook ​

عندما تصل المهمة إلى الحالة done أو failed، يُرسل إلى callback_url لديك طلب POST يتضمن المحتوى نفسه الذي يعيده GET /media/job/:id، بالإضافة إلى الحقل event:

json
{ "event": "media.completed", "id": "beefb531-…", "status": "done",
  "result_url": "https://nordrouter.com/media/file/beefb531-…",
  "cost_usd": 0.052, "error": null }
  • event — إما media.completed (نجاح) أو media.failed (خطأ).

التحقق من التوقيع ​

إذا عيّنت callback_secret، فسيحتوي الترويسة X-NR-Signature على sha256=<hmac> — وهو HMAC-SHA256 لمحتوى جسم الطلب الخام، باستخدام سرك مفتاحًا. تحقّق منه للتأكد من أن الطلب صادر منّا وليس مزوّرًا:

python
import hmac, hashlib

def verify(raw_body: bytes, header: str, secret: str) -> bool:
    expected = "sha256=" + hmac.new(secret.encode(), raw_body, hashlib.sha256).hexdigest()
    return hmac.compare_digest(expected, header)  # مقارنة بزمن ثابت
javascript
import crypto from "node:crypto";
function verify(rawBody, header, secret) {
  const expected = "sha256=" + crypto.createHmac("sha256", secret).update(rawBody).digest("hex");
  return crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(header));
}

احسب HMAC من البايتات كما وصلت تمامًا، قبل تحليل JSON، وإلا فلن يتطابق التوقيع.

مستقبِل جاهز في 10 أسطر ​

خادم يعمل ويستقبل Webhook ويتحقق من التوقيع ويردّ برمز 2xx. السر هو نفسه الذي أرسلته في callback_secret.

Node.js / Express:

javascript
import express from "express";
import crypto from "node:crypto";

const SECRET = "любая-ваша-строка";           // = ваш callback_secret
const app = express();

app.post("/webhooks/nordrouter",
  express.raw({ type: "application/json" }),   // сырое тело — обязательно для подписи
  (req, res) => {
    const expected = "sha256=" + crypto.createHmac("sha256", SECRET).update(req.body).digest("hex");
    const got = req.headers["x-nr-signature"] || "";
    if (expected.length !== got.length || !crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(got)))
      return res.sendStatus(401);             // подпись не сошлась — не наш запрос
    const job = JSON.parse(req.body);
    if (job.event === "media.completed") console.log("готово:", job.result_url, "$" + job.cost_usd);
    res.sendStatus(200);                       // ответьте 2xx, иначе будет повтор
  });

app.listen(3000);

Python / FastAPI:

python
import hmac, hashlib
from fastapi import FastAPI, Request, Response

SECRET = b"любая-ваша-строка"                  # = ваш callback_secret
app = FastAPI()

@app.post("/webhooks/nordrouter")
async def hook(request: Request):
    raw = await request.body()                 # сырые байты — обязательно для подписи
    expected = "sha256=" + hmac.new(SECRET, raw, hashlib.sha256).hexdigest()
    if not hmac.compare_digest(expected, request.headers.get("X-NR-Signature", "")):
        return Response(status_code=401)       # подпись не сошлась — не наш запрос
    job = await request.json()
    if job["event"] == "media.completed":
        print("готово:", job["result_url"], "$", job["cost_usd"])
    return Response(status_code=200)           # ответьте 2xx, иначе будет повтор

التحقق محليًا

يجب أن يكون callback_url عنوان https عامًا. للتطوير على حاسوبك المحمول، أنشئ نفقًا، مثل ngrok http 3000، وأرسل عنوان https الذي يوفّره بوصفه callback_url.

التسليم والموثوقية ​

  • حتى 4 محاولات مع فواصل زمنية متزايدة (≈3 → 6 → 9 ثوانٍ)، ومهلة كل محاولة 15 ثانية. أعدّ 2xx، وإلا فستُعاد المحاولة.
  • لا تُتبع عمليات إعادة التوجيه — أجب مباشرةً على callback_url.
  • التسليم بأفضل جهد: إذا فشلت المحاولات الأربع كلها (مثلًا، كان خادمك متوقفًا)، فلن يُعاد إرسال Webhook لاحقًا. لذلك:

Webhooks لا تحل محل الاستعلام

Webhook وسيلة لتسريع الإشعار، وليس ضمانًا. أبقِ GET /media/job/:id مسارًا احتياطيًا: إذا لم يصل الإشعار الفوري خلال مدة معقولة، فتحقّق من الحالة بنفسك. تقع مسؤولية ضمان عدم تكرار المعالجة على جانبك: قد تصلك المهمة نفسها عبر Webhook وتلاحظها من خلال الاستعلام، لذا اعتمد على id.

متطلبات callback_url ​

  • يجب أن يبدأ بـ https:// فقط.
  • يجب أن يكون عنوانًا عامًا فقط. تُرفَض العناوين المحلية والخاصة (localhost و*.local و10.x و172.16–31.x و192.168.x و127.x و169.254.x و100.64–127.x وعناوين IPv6 loopback/ULA/link-local) للحماية من SSRF. ويُعاد التحقق من العنوان قبل كل إرسال أيضًا (للحماية من DNS rebinding).

ماذا تختار ​

الاستعلامWebhook
عنوان https عامغير مطلوبمطلوب
تأخير الاستلامحتى فترة الاستعلامشبه فوري
الضمانتتحكم به بنفسكأفضل جهد + الاستعلام كمسار احتياطي
الأنسب لـالسكربتات وCI والحاسوب المحمولالخوادم والأنظمة الخلفية للإنتاج

التوصية: في الأنظمة الخلفية، استخدم Webhook مع الاستعلام كإجراء احتياطي؛ وفي السكربت أو على الحاسوب المحمول، استخدم الاستعلام فقط.