Product
دست از پرسوجوی مدام برای کاری که تمام شده بردارید
وقتی کار تمام شد خبردار شوید، و بدانید چه کسی خبرتان کرده است.
By Ehsan Gazar, ۲۸ شهریور ۱۴۰۵
جایی در کد شما حلقهای هست که هر سی ثانیه یک سؤال تکراری میپرسد: تمام شد؟ بیشتر وقتها پاسخ منفی است، و این حلقه تنها جزء سیستم شماست که از وجود آن کار خبر دارد.
پرسوجوی مکرر (polling) کار میکند. فقط انتظار را در جای اشتباهی میگذارد.
حلقهای که مالکی ندارد
درگاه مدل بیشتر با درخواست و پاسخ سروکار دارد. میپرسید، یک ثانیه صبر میکنید و پاسخ میگیرید. کاری که در این قالب نمیگنجد، همان کاری است که ساعت سه بامداد از همه مهمتر است.
دستهای از ده هزار طبقهبندی که با مهلت ۲۴ ساعته به یک ارائهدهنده سپرده شده است. عاملی که طبق زمانبندی اجرا میشود، وقتی همه خواباند. گردشکاری که محصول خودتان از طریق API آغاز کرده است. پوشهای از اسناد که پیش از آنکه کسی بتواند در آن جستوجو کند، قطعهبندی و نمایهسازی میشود. و زیر همهی اینها، موجودیای که رو به کاهش است.
هر کدام در لحظهای تمام میشود که شما انتخابش نکردهاید. پس کسی یک حلقه مینویسد، و آن حلقه به سرویس کوچکی تبدیل میشود که هیچکس یادش نیست مالکش کیست. وقتی از کار بیفتد، چیزی به شما خبر نمیدهد. دسته تمام میشود، نتایج همانجا میمانند، و نخستین نشانهی مشکل، مشتریای است که میپرسد گزارشش کجا رفته است.
راهحل، حلقهای سریعتر نیست. راهحل این است که جهت سؤال را برعکس کنید.
خبرتان میکنند، لازم نیست بپرسید
وبهوک نشانیای است که به ما میدهید. هر وقت رویدادی در حساب شما رخ دهد، به آن نشانی درخواست POST میفرستیم.
درگاه ما امروز دوازده رویداد میفرستد. batch.finished، همراه با اینکه دسته چگونه پایان یافت و چند مورد از آن ناموفق بود. agent.run.succeeded و agent.run.failed، همراه با کل پاسخ، نه فقط پیشنمایشی از آن. workflow.run.succeeded و workflow.run.failed برای اجراهایی که محصول شما با یک کلید آغاز کرده است. knowledge.collection.ready، درست در لحظهای که مجموعهای که در حال پر کردنش هستید قابل جستوجو میشود، و knowledge.collection.attention وقتی سندی در آن خوانده نشد. بعد رویدادهای مالی: balance.low، balance.exhausted و key.budget_exhausted. و دو رویداد دربارهی کلیدها: key.expiring و key.unused.
هر ارسال، فارغ از نوع رویداد، پوشش یکسانی دارد.
POST https://your-app.example.com/vatan
x-vatan-signature: 4c1f...
x-vatan-timestamp: 1789787529
x-vatan-event-id: evt_...
x-vatan-attempt: 1
{
"id": "evt_...",
"event": "batch.finished",
"at": "2026-09-19T03:12:09.112Z",
"data": {"batch_id": "...", "api_status": "completed", "total": 2, "completed": 2, "failed": 0}
}
یک وبهوک میتواند همهی رویدادها را بگیرد، یا فقط آنهایی را که نام میبرید. سامانهی هشدار کشیک (pager) به balance.exhausted نیاز دارد. سرویسی که نتایج دستهها را جمع میکند، فقط batch.finished را میخواهد و هیچ چیز دیگر.
غریبهای که نشانی شما را دارد
گیرندهی شما یک نشانی عمومی است، پس هر کسی که از آن باخبر شود میتواند به آن درخواست POST بفرستد. بدون بررسی، وبهوک غریبهای است که به سیستمهای شما میگوید موجودیتان مشکلی ندارد.
برای همین هر ارسال امضا میشود. ما یک HMAC-SHA256 روی مُهر زمانی، یک نقطه و بایتهای دقیق بدنه محاسبه میکنیم. کلید آن رازی است که هنگام ساخت وبهوک فقط یک بار به شما نشان داده میشود. گیرندهی شما همین محاسبه را انجام میدهد و نتیجه را مقایسه میکند.
import { createHmac, timingSafeEqual } from 'node:crypto'
function fromVatan(secret, rawBody, headers) {
const ts = headers['x-vatan-timestamp']
const expected = createHmac('sha256', secret).update(`${ts}.${rawBody}`).digest('hex')
const given = Buffer.from(headers['x-vatan-signature'] ?? '')
const fresh = Math.abs(Date.now() / 1000 - Number(ts)) < 300
return fresh && given.length === expected.length && timingSafeEqual(given, Buffer.from(expected))
}
مُهر زمانی عمداً جزو بایتهای امضاشده است. کسی که ارسالی قدیمی را دوباره پخش میکند نمیتواند آن را تغییر دهد، مگر اینکه امضا را خراب کند؛ برای همین بررسی قدمت پیام معنا پیدا میکند.
وقتی گیرندهی شما از دسترس خارج است
گیرندهها ریاستارت میشوند. هر استقرار یک دقیقه طول میکشد. ارسالی که با وقفهی زمانی (timeout)، یا کد 408، 429 یا 5xx روبهرو شود، پس از یک دقیقه دوباره امتحان میشود، سپس پس از پنج دقیقه، سی دقیقه، دو ساعت و هشت ساعت. یعنی شش تلاش در حدود ده ساعت و نیم.
هر تلاش همان شناسهی رویداد و همان بدنه را دارد و با مُهر زمانی تازه، از نو امضا میشود. همین است که تلاش دوباره را بیخطر میکند: گیرندهای که قبلاً برای یک شناسه اقدام کرده، پاسخ 200 میدهد و کاری نمیکند.
برای 404 یا 401 تلاش دوبارهای انجام نمیشود. این یعنی گیرنده میگوید نشانی دیگر وجود ندارد یا ما را نمیپذیرد، و پرسیدن دوباره چیزی را تغییر نمیدهد. هر تلاش، همراه با وضعیتی که گیرندهی شما پاسخ داده، در گزارش ارسالها ثبت میشود، و هر ارسالی از هفت روز گذشته را میتوان دستی دوباره فرستاد.
دیدن نخستین رویداد، پیش از آنکه چیزی بسازید
سختترین بخش وبهوک، نخستین رویداد است. گیرندهای مینویسید، مستقرش میکنید و منتظر رویدادی میمانید که بهآسانی نمیتوانید ایجادش کنید.
برای همین یک وبهوک میتواند بهجای آن به گیرندهای از خود ما اشاره کند. این گیرنده صد درخواست آخری را که به آن میرسد نگه میدارد و امضای هر کدام را همانطور بررسی میکند که کد شما باید بررسی کند. همچنین میتوانید نمونهی هر رویداد را به هر وبهوکی بفرستید. این نمونه با علامت "test": true میرسد، تا هیچ بخشی در ادامهی مسیر بر اساس آن اقدامی نکند.
حلقهای که مدام میپرسید دیگر نیست. جایش را نشانیای گرفته که به آن خبر داده میشود.