پرش به مستندات
پایگاه دانش

وب‌هوک‌ها

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

جزئیات

  • از همین امروز از تنظیمات ← وب‌هوک‌ها و روی API قابل استفاده است: یک نقطه‌ی پایانی https ثبت کنید، از میان آن بیست رویداد آن‌هایی را که می‌خواهد انتخاب کنید، و رمز امضای whsec_ را کپی کنید، که هنگام ساخت و هنگام چرخاندن نشان داده می‌شود و دیگر هرگز. تحویل‌ها POSTهای امضاشده‌ی واقعی‌اند که خودِ صندوق پستی برمی‌انگیزد نه هیچ فراخوانی APIای، پس روی نامه‌ی ورودی و روی بازشدن‌ها و کلیک‌ها شلیک می‌کنند، هرچه که پیام را فرستاده باشد. ارسال از هر سطحی شلیک می‌کند، و پیش‌تر فقط از بعضی شلیک می‌کرد: ارسالی از راه API، MCP، یک قالب یا یک قاعده email.sent را برمی‌انگیخت اما پیامی که از بخش نگارشِ خودِ برنامه فرستاده می‌شد نه، چون بخش نگارش مستقیم در صندوق پستی می‌نویسد نه از راه سرویس ارسالی که آن رویداد را منتشر می‌کرد. حالا رویداد در خودِ صندوق پستی برانگیخته می‌شود، جایی که همه‌شان به هم می‌رسند، پس نوشتن در برنامه، زمان‌بندی برای سه‌شنبه و POST به API سه راه برای برانگیختن یک وب‌هوک‌اند. ارسال تعویقی دو بار خبر می‌دهد: email.scheduled یا email.queued وقتی پذیرفته می‌شود، email.sent وقتی واقعاً می‌رود، و email.cancelled اگر در این میان پسش بگیرید. ده نقطه‌ی پایانی برای هر صندوق پستی، که هرجا یکی ثبت شود اعمال می‌شود نه فقط در این صفحه.
  • رویدادها در سه خانواده می‌آیند. پانزده‌تا درباره‌ی یک پیام‌اند: email.received، email.replied، email.sent، email.delivered، email.failed، email.cancelled، email.scheduled، email.queued (خواهرِ لغو-ارسالِ scheduled)، email.delivery_delayed، email.bounced، email.complained، email.suppressed، email.opened، email.clicked و email.downloaded. email.sent یعنی سرویس ارسال پیام را پذیرفت، email.delivered یعنی سرور گیرنده آن را پذیرفت، و email.delivery_delayed یعنی هنوز نرسیده و همچنان تلاش دوباره می‌شود. email.replied در کنار email.received شلیک می‌کند وقتی پیام رسیده پاسخِ پیامی است که از پیش در صندوق پستی هست، پس مصرف‌کننده‌ای که هر دو را بخواهد هر دو را می‌گیرد. email.downloaded وقتی شلیک می‌کند که کسی فایلی را که به‌صورت لینک دانلود بیرون رفته بگیرد، با همان دسته‌بندی‌کننده‌ای که پویشگرها و پیش‌نمایش‌گرهای لینک را از شمارش بیرون نگه می‌دارد، و هیچ گیرنده‌ای را نام نمی‌برد، چون آن لینک برای همه‌ی کسانی که پیام به آن‌ها رفته یکی است. سه‌تا درباره‌ی یک دامنه‌اند: domain.verified وقتی شروع به دریافت می‌کند، domain.sending_changed وقتی حکم ارسالش جابه‌جا می‌شود، و domain.deleted وقتی برداشته می‌شود، چه شما خواسته باشید چه درو‌کننده‌ی هفت‌روزه آن را تأییدنشده انداخته باشد. دوتا درباره‌ی خودِ فهرست سرکوب‌اند، که چیزی جدا از email.suppressed است: suppression.added وقتی آدرسی روی آن می‌رود، suppression.removed وقتی دوباره اجازه می‌یابد. مشترک‌نشدن در هیچ‌کدام یعنی همه‌ی رویدادهای پیام جز email.replied، که امروز چهارده‌تاست، و هرگز خانواده‌ای که بعداً افزوده شود، و API آن را به‌صورت ["*"] بازمی‌خواند. اگر ترجیح می‌دهید صریح باشید، رویدادهایی را که می‌خواهید نام ببرید. هر تحویل X-OpenEmail-Signature را به شکل t=<unix>,v1=<hex> حمل می‌کند، یک HMAC-SHA-256 روی برچسب زمانی، یک نقطه، و بدنه‌ی خام، به‌علاوه‌ی X-OpenEmail-Event و X-OpenEmail-Delivery. در برابر همان بایت‌هایی که رسیده‌اند راستی‌آزمایی کنید: تجزیه و دوباره‌سریال‌سازی ترتیب کلیدها را به هم می‌زند و امضا را می‌شکند. پنجره‌ی بازپخش 300 ثانیه‌ای بر عهده‌ی گیرنده است، و راستی‌آزمای SDK همان را پیش‌فرض می‌گیرد.
  • ثبت‌نام برای هر چیزی که https نباشد یا به‌طور عمومی قابل مسیریابی نباشد رد می‌شود (loopback، RFC1918، link-local، CGNAT و معادل‌های IPv6 آن‌ها)، و redirect دنبال نمی‌شود، پس یک 3xx به‌جای اینکه جای دیگری تعقیب شود، به‌عنوان تحویل ناموفق ثبت می‌شود. گیرنده 5 ثانیه وقت دارد، نقطه‌های پایانی به‌موازات تحویل می‌گیرند پس ده‌تای آن‌ها هم 5 ثانیه خرج می‌کنند نه 50، و همه‌ی تلاش‌ها، صفحه به صفحه، در صفحه‌ی همان نقطه‌ی پایانی با کد پاسخ و مدت زمانی که برد فهرست می‌شوند.
  • هر تحویل تا 8 بار تلاش می‌شود. اولی همان لحظه‌ی رخ‌دادن رویداد بیرون می‌رود؛ شکستی که به‌طور معقول ممکن است خودبه‌خود رفع شود پس از 1 دقیقه، بعد 5، بعد 30، بعد 2 ساعت، 5 ساعت، 10 ساعت و 10 ساعت دیگر دوباره تلاش می‌شود، که یک رویداد را روی حدود 27 ساعت‌ونیم پخش می‌کند. هر انتظار تا یک‌دهم کم‌وزیاد می‌شود تا هزار رویدادی که با هم شکست خورده‌اند همه در یک ثانیه برنگردند، و Retry-After نقطه‌ی پایانی اگر زمان بیشتری بخواهد تا 6 ساعت رعایت می‌شود. تلاش‌های دوباره به‌جای حافظه، به‌صورت کار ماندگار نگه داشته می‌شوند، پس یک deploy وسط آن پنجره آن‌ها را از بین نمی‌برد. فقط شکست‌هایی که ارزش تکرار دارند تکرار می‌شوند: یک timeout، اتصال ردشده، 408، 425، 429 یا هر 5xx. هر 4xx دیگری یعنی نقطه‌ی پایانی عمداً محموله را رد می‌کند، و هفت بار دیگر پرسیدن یعنی هفت برابر بار برای همان پاسخ. شناسه‌ی رویداد و createdAt آن یک‌بار تثبیت می‌شوند و هر تلاش هر دو را حمل می‌کند، شناسه را در X-OpenEmail-Delivery هم، پس گیرنده‌ای که یک شناسه را دو بار ببیند می‌تواند دومی را بیندازد نه اینکه دو بار روی آن عمل کند. وقتی نقطه‌ی پایانی درست شد، تحویلی را که شکست خورده می‌توان از گزارش تحویل در برنامه یا از راه API دوباره پخش کرد، هر بار یک رویداد، و بازپخش همان شناسه را حمل می‌کند. بازپخش تا وقتی فرستاده می‌شود تلاش‌های دوباره‌ی خودکار آن رویداد را متوقف می‌کند، و اگر یکی از آن‌ها همان لحظه در حال فرستاده شدن باشد رد می‌شود، تا گیرنده هرگز دو نسخه را هم‌زمان نگیرد. پس از آنکه 100 رویداد پشت‌سرهم در همه‌ی تلاش‌ها شکست بخورند، نقطه‌ی پایانی غیرفعال می‌شود، به فضای کاری ایمیل زده می‌شود، و دلیلش روی خودِ نقطه‌ی پایانی خواندنی است. نقطه‌ی پایانی‌ای که 410 Gone پاسخ دهد همان‌جا غیرفعال می‌شود.
  • نقطه‌ی پایانی‌ای که 100 بار پشت‌سرهم شکست بخورد به‌جای اینکه تا ابد شماره‌گیری شود خاموش می‌شود، و به همه‌ی کسانی که دسترسی وب‌هوک دارند ایمیل زده می‌شود تا بدانند: کدام یکی، آخرین تلاش چه گزارش کرد، و اینکه در مدتی که شکست می‌خورد چیزی در صف نمانده است. این شمارش پشت‌سرهم است و هر تلاش موفق آن را صفر می‌کند، پس یک بعدازظهر بدِ اسفندِ پارسال نمی‌تواند امروز جمع بزند و نقطه‌ی پایانی را غیرفعال کند. روشن‌کردن دوباره‌اش شمارش را هم پاک می‌کند. کنسول این دو وضعیت را از هم جدا نشان می‌دهد نه با یک کلید واحد: نقطه‌ی پایانی‌ای که شما خاموش کرده‌اید با آنکه ما خاموش کرده‌ایم فرق دارد.
  • مدیریت نقطه‌های پایانی یک کار با دو درِ ورودی است. روی API این می‌شود POST /webhooks، آن patch، آن delete، rotate-secret، test، گزارش تحویل و بازپخش، با یک متد برای هرکدام در SDK؛ در برنامه می‌شود تنظیمات ← وب‌هوک‌ها، روی همان مخزن ثبت، نه یک مخزن دوم. خواندن پشت webhooks:read است، پس هرکسی که یکپارچه‌سازی می‌سازد می‌تواند نقطه‌های پایانی و تاریخچه‌ی تحویلشان را ببیند (کدام شلیک کرد، گیرنده چه پاسخ داد، چقدر طول کشید) بدون آنکه مالک باشد. ثبت، ویرایش، آزمایش، چرخاندن، بازپخش و حذف، هم webhooks:write و هم مالکیت صندوق پستی را لازم دارند، روی هر دو سطح، و آن نیمه‌ی دوم عمدی است: یک نقطه‌ی پایانی از هر آدرسی که فضای کاری دارد باخبر می‌شود، مگر آنکه فهرست‌های مجاز خودش آن را محدود کنند، با موضوع و گیرنده‌هایش، و نداشتن مجوز یعنی «ممکن است همه‌ی این‌ها برایش فرستاده شود». نقشی که یکپارچه‌سازی می‌سازد و نامه‌ها را نمی‌خواند، به‌جایش با یک کلید فضای کاری آن را می‌گرداند. باز کردن یک تحویل برای خواندن بدنهٔ فرستاده‌شده و کل پاسخ هم مالکیت می‌خواهد، چون آن بدنه همان موضوع‌ها و گیرندگان را در خود دارد.
  • گزارش‌ها همه‌جا به یک شکل خوانده می‌شوند. GET /webhooks/deliveries گزارش تحویل همهٔ نقاط پایانی را یکجا می‌خواند و GET /webhooks/{id}/deliveries گزارش یکی را، هر دو محدود به وضعیت، یعنی کلید «فقط ناموفق‌ها»، و به یک بازهٔ زمانی، و GET /webhooks/activity و GET /webhooks/{id}/activity می‌خوانند چه کسی چه چیزی را ساخت، تغییر داد، خاموش یا روشن کرد، چرخاند، آزمود، دوباره فرستاد یا حذف کرد، به‌صورت @username یا با نام کلید API‌ای که آن را انجام داده است. SDK برای هرکدام متدی دارد و سرور MCP ابزارهای listWebhookDeliveries، getWebhookDelivery، listWebhookActivity و replayWebhookDelivery را دارد که همیشه پیش از ارسال می‌پرسد. خواندن بدنهٔ یک تحویل در همهٔ سطوح فقط در اختیار مالک است، و کلیدی که به یک نشانی از یک دامنه محدود است نمی‌تواند تحویل‌های نقطهٔ پایانی‌ای را بخواند که کل دامنه را پوشش می‌دهد.