رفتن به محتوای اصلی
مستندات APIمسیرهاWebhooks

وب‌هوک‌ها: دریافت خودکار نتیجه

به‌جای پیگیری مداوم، نتیجهٔ هر تسک به‌محض آماده شدن با یک درخواست امضاشده به سرور شما ارسال می‌شود. رویدادها، ساختار بدنه، بررسی امضا و تلاش مجدد.

چرا وب‌هوک؟

تولید عکس و ویدیو ناهمگام است. به‌جای اینکه هر چند ثانیه GET /api/v1/tasks/:id را صدا بزنید، یک نشانی HTTPS ثبت کنید تا به‌محض تمام شدن هر تسک، یک درخواست POST امضاشده با نتیجهٔ کامل به سرور شما ارسال شود. این کار سهمیهٔ نرخ درخواست شما را هم مصرف نمی‌کند.

  1. 1

    نشانی را در پنل ثبت کنید

    در پنل توسعه‌دهندگان ← وب‌هوک‌ها نشانی را وارد و رویدادها را انتخاب کنید. کلید امضای whsec_… فقط همان لحظه نمایش داده می‌شود؛ آن را در متغیر محیطی سرور نگه دارید. تا ۵ نشانی می‌توانید ثبت کنید.
  2. 2

    رویداد آزمایشی بفرستید

    دکمهٔ «ارسال آزمایشی» یک رویداد webhook.test امضاشده می‌فرستد و کد پاسخ سرور شما را همان‌جا نشان می‌دهد.
  3. 3

    امضا را بررسی و سریع پاسخ دهید

    امضا را با کلید بررسی کنید، ظرف ۱۰ ثانیه کد 2xx برگردانید و پردازش سنگین را در صف انجام دهید.

رویدادها

رویدادچه زمانی
task.completedتسک عکس، ویدیو یا دوبلهٔ ساخته‌شده با API با موفقیت تمام شد. نشانی خروجی‌ها در data.images، data.videos یا data.audios است.
task.failedتسک ناموفق شد (خطای مدل، فایل مرجع نامعتبر یا پایان مهلت ۱۵ دقیقه). علت در data.error است و اعتبار رزروشده برگشته.
webhook.testفقط با دکمهٔ «ارسال آزمایشی» در پنل ارسال می‌شود؛ داده‌ها نمونه‌اند.

اطلاعات

فقط تسک‌هایی که با کلید API ساخته شده‌اند رویداد می‌فرستند؛ تولیدهای استودیو وب‌هوک ندارند. گفتگو (chat/completions) همان لحظه پاسخ می‌دهد و به وب‌هوک نیاز ندارد.

ساختار درخواست

بدنه JSON است و data دقیقاً همان ساختار تسک است، همراه با metadata که هنگام ساخت تسک فرستاده‌اید:

JSON
1{
2  "id": "evt_4mZ0q9sJ2bN7xY1vT8cK3pLd",
3  "type": "task.completed",
4  "created_at": "2026-09-27T10:21:07.000Z",
5  "data": {
6    "id": "task_abc123",
7    "status": "completed",
8    "type": "image",
9    "model": "nano-banana-2",
10    "prompt": "فنجان قهوه روی میز چوبی، نور صبح",
11    "images": ["https://cdn.bananaai.ir/.../output.png"],
12    "videos": [],
13    "audios": [],
14    "error": null,
15    "credits_reserved": 8,
16    "credits_deducted": true,
17    "metadata": { "order_id": "1042" },
18    "created_at": "2026-09-27T10:20:41.000Z",
19    "completed_at": "2026-09-27T10:21:07.000Z"
20  }
21}

هدرهای هر درخواست:

BananaAI-Signaturestring
قالب t=1790508968,v1=5f2c…. مقدار v1 برابر HMAC-SHA256 (hex) از رشتهٔ {t}.{rawBody} با کلید امضای وب‌هوک است.
BananaAI-Eventstring
نوع رویداد؛ همان type داخل بدنه.
BananaAI-Deliverystring
شناسهٔ یکتای این ارسال. در تلاش‌های مجدد ثابت می‌ماند؛ برای حذف رویدادهای تکراری از آن استفاده کنید.
BananaAI-Timestampstring
زمان امضا (ثانیه یونیکس)؛ همان t در امضا.

بررسی امضا

امضا را روی بدنهٔ خام و پیش از JSON.parse بررسی کنید؛ هر تغییری در فاصله‌ها یا ترتیب کلیدها امضا را باطل می‌کند. مقایسه را زمان‌ثابت انجام دهید و رویدادهایی با t قدیمی‌تر از ۵ دقیقه را رد کنید.

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

const app = express();
const SECRET = process.env.BANANA_WEBHOOK_SECRET; // whsec_...
const seen = new Set(); // در محصول: Redis یا جدول پایگاه‌داده

app.post(
  "/webhooks/banana",
  express.raw({ type: "application/json" }), // بدنهٔ خام برای امضا
  (req, res) => {
    const header = req.get("BananaAI-Signature") ?? "";
    const { t, v1 } = Object.fromEntries(header.split(",").map((p) => p.split("=")));
    const raw = req.body.toString("utf8");

    const expected = crypto.createHmac("sha256", SECRET).update(`${t}.${raw}`).digest("hex");
    const valid =
      v1?.length === expected.length &&
      crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(v1));
    const fresh = Math.abs(Date.now() / 1000 - Number(t)) < 300;
    if (!valid || !fresh) return res.status(400).send("invalid signature");

    // هر رویداد ممکن است بیش از یک بار برسد
    const deliveryId = req.get("BananaAI-Delivery");
    if (seen.has(deliveryId)) return res.sendStatus(200);
    seen.add(deliveryId);

    const event = JSON.parse(raw);
    res.sendStatus(200); // سریع پاسخ بدهید، کار سنگین را در صف انجام دهید

    if (event.type === "task.completed") {
      saveOutputs(event.data.metadata.order_id, event.data.images, event.data.videos);
    } else if (event.type === "task.failed") {
      markFailed(event.data.metadata.order_id, event.data.error);
    }
  }
);

پاسخ و تلاش مجدد

  • هر پاسخ 2xx در کمتر از ۱۰ ثانیه یعنی تحویل موفق. ریدایرکت دنبال نمی‌شود و ناموفق حساب می‌شود.
  • در صورت خطا یا تمام شدن مهلت، تا ۶ بار دوباره ارسال می‌شود: پس از ۱ دقیقه، ۵ دقیقه، ۳۰ دقیقه، ۲ ساعت و ۸ ساعت (حدود ۱۱ ساعت در مجموع).
  • ترتیب رسیدن رویدادها تضمین نمی‌شود و ممکن است یک رویداد بیش از یک بار برسد؛ با BananaAI-Delivery یا data.id تکراری‌ها را کنار بگذارید.
  • پس از ۲۵ ارسال ناموفق پیاپی، وب‌هوک خودکار غیرفعال می‌شود. پس از رفع مشکل، آن را از پنل دوباره فعال کنید.
  • گزارش ۳۰ روز اخیر ارسال‌ها، بدنهٔ دقیق و پاسخ سرور شما در پنل قابل مشاهده است و هر ارسال را می‌توانید دوباره بفرستید.

امنیت

  • فقط نشانی‌های https:// عمومی پذیرفته می‌شوند؛ IPهای خصوصی و شبکهٔ داخلی مسدودند.
  • اگر کلید امضا لو رفت، از منوی وب‌هوک «کلید امضای جدید» را بزنید؛ کلید قبلی بلافاصله باطل می‌شود.
  • نشانی خروجی‌ها را دانلود و روی فضای خودتان نگه دارید. برای نشانی‌های ماندگار، پس از رویداد می‌توانید تسک را با GET /api/v1/tasks/:id هم بخوانید.

وب‌هوک در کنار پیگیری

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