به محتوای اصلی

وب‌هوک

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

هر بار که رکوردی ساخته، ویرایش یا حذف شود، فیکارو یک POST به آدرس شما می‌فرستد.

ساختن

در پنل پروژه، بخش وب‌هوک‌ها: یک نام، آدرس مقصد، و اینکه کدام موجودیت‌ها و کدام رویدادها. اگر موجودیتی انتخاب نکنید یعنی همه‌ی موجودیت‌ها؛ رویدادها پیش‌فرض هر سه‌تا (create، update، delete) است.

هنگام ساخت، یک secret امضا با پیشوند whsec_ تولید می‌شود. همان را نگه دارید — بخش «راستی‌آزمایی» پایین به آن نیاز دارد.

آدرس مقصد باید عمومی باشد. آدرس‌های شبکه‌ی داخلی (127.0.0.1، رنج‌های خصوصی، 169.254.169.254) عمداً رد می‌شوند: سروری که هر آدرسی را صدا بزند، تبدیل به پروکسیِ درخواست به شبکه‌ی خودش می‌شود.

بدنه‌ی رویداد

{
  "id": "01J...",
  "type": "order.create",
  "entity": "order",
  "op": "create",
  "timestamp": "2026-09-05T12:00:00Z",
  "record": { "id": "...", "total": 250000, "status": "new" },
  "old": { }
}

old فقط در update پر است و مقدار پیش از تغییر را دارد. در delete، record آخرین وضعیت رکورد حذف‌شده است.

هدرها

Content-Type: application/json; charset=utf-8
X-Fikaro-Signature:  sha256=<hex>
X-Fikaro-Timestamp:  <unix seconds>
X-Fikaro-Webhook-Id: <شناسه وب‌هوک>

راستی‌آزمایی امضا

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

امضا HMAC-SHA256 روی رشته‌ی "<timestamp>.<body>" با secret شماست — یعنی زمان داخل چیزی است که امضا می‌شود، نه یک هدر مستقل. به همین دلیل درخواستِ ضبط‌شده را نمی‌شود بعداً با زمان تازه دوباره فرستاد.

import crypto from "node:crypto";

function verify(rawBody, headers, secret) {
  const ts  = headers["x-fikaro-timestamp"];
  const got = headers["x-fikaro-signature"];

  // بدنه را خام بگیرید، نه JSON پارس‌شده و دوباره رشته‌شده —
  // یک فاصله یا ترتیب کلید متفاوت، امضا را می‌شکند.
  const expected = "sha256=" + crypto
    .createHmac("sha256", secret)
    .update(ts + "." + rawBody)
    .digest("hex");

  if (!crypto.timingSafeEqual(Buffer.from(got), Buffer.from(expected))) {
    return false;
  }
  // رویداد کهنه را رد کنید (مثلاً بیش از ۵ دقیقه).
  return Math.abs(Date.now() / 1000 - Number(ts)) < 300;
}

مقایسه را با timingSafeEqual بکنید، نه ===.

تلاش مجدد

پاسخ 2xx یعنی تحویل شد. هر چیز دیگری تلاش مجدد می‌گیرد:

| تلاش | فاصله تا بعدی | |---|---| | ۱ | ۳۰ ثانیه | | ۲ | ۵ دقیقه | | ۳ | ۳۰ دقیقه | | ۴ | ۲ ساعت | | ۵ | ۶ ساعت |

بعد از ۵ تلاش تحویل «مرده» علامت می‌خورد. اگر ۲۰ تحویل پشت‌سرهم شکست بخورد، خودِ وب‌هوک غیرفعال می‌شود — وگرنه یک مقصد همیشه‌خراب صف را اشغال می‌کند و وب‌هوک‌های سالم پشتش گم می‌شوند. بعد از رفع مشکل، از پنل دوباره فعالش کنید؛ شمارنده صفر می‌شود.

دو چیز که باید در کدتان فرض کنید

تحویل ممکن است تکرار شود. تلاش مجدد یعنی یک رویداد می‌تواند دو بار برسد. id رویداد را ذخیره کنید و تکراری را نادیده بگیرید — یعنی هندلرتان باید idempotent باشد.

رویداد ممکن است رکوردی را بگوید که دیگر نیست. رویداد بعد از عملیات منتشر می‌شود، و تراکنشی که پس از آن برگردد می‌تواند رویدادِ رکوردی را جا بگذارد که وجود ندارد. پیش از تکیه بر آن، رکورد را از API بخوانید.

بیشتر بخوانید

  • قوانین — منطقی که داخل فیکارو اجرا می‌شود
  • فانکشن‌ها — اگر می‌خواهید خودتان وب‌هوکِ کسِ دیگری را بگیرید
  • کار با API — خواندن رکورد پس از دریافت رویداد