پرش به مستندات
Python

اندپوینت‌ها

`webhooks.list`، `list_all`، `iterate`، `get`، `create`، `update`، `delete`، `rotate_secret`، `test`، `get_delivery` و `replay_delivery`، به‌همراه گزارش‌های تحویل و فعالیت.

همهٔ متدها

usage.py
from acme.secrets import store endpoint = client.webhooks.create({    'url': 'https://acme.com/hooks/mail',    'eventTypes': ['email.sent', 'email.bounced'],    'description': 'Billing service',}) store(endpoint['secret']) client.webhooks.list()client.webhooks.get(endpoint['id'])client.webhooks.update(endpoint['id'], {'enabled': False})client.webhooks.test(endpoint['id'])latest = client.webhooks.list_deliveries(endpoint['id'], limit=1)['items'][0]client.webhooks.get_delivery(endpoint['id'], latest['id'])client.webhooks.replay_delivery(endpoint['id'], latest['id'])rotated = client.webhooks.rotate_secret(endpoint['id'])store(rotated['secret'])client.webhooks.delete(endpoint['id'])

create جز rotate_secret تنها جایی است که کلید مخفی بازگردانده می‌شود. خواندن هرگز آن را بازتاب نمی‌دهد، پس پیش از هر کار دیگری ذخیره‌اش کنید. برای مجموعهٔ پیش‌فرض، یعنی هر رویداد email.* جز email.replied، eventTypes را ندهید. email.replied، domain.*، suppression.*، file.* و form.* تنها وقتی به یک اندپوینت می‌رسند که آن اندپوینت نامشان را ببرد.

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

چه چیزهایی را می‌توان اشتراک گرفت

WEBHOOK_EVENTS export می‌شود تا بتوانید فهرست را رندر کنید. رویدادها، رویدادهای **صندوق پستی** هستند نه این API: email.received برای ایمیلی که در اپ می‌رسد شلیک می‌شود، و email.sent برای پیامی که کامپوزر فرستاده است. مشترک شدن با تماشای ترافیک API خودتان یکی نیست.

file.uploaded وقتی شلیک می‌کند که فایلی در صفحهٔ فایل‌ها گذاشته شود، و file.deleted وقتی یکی حذف شود. داده‌شان FileEventData است: fileId، filename، mimeType، sizeBytes، direction، to، threadId، messageId، و uploadedAt یا deletedAt. to نشانی‌ای است که فایل به آن تعلق دارد، یا null برای فایلی که به کل فضای کاری تعلق دارد.

رویدادهای فایل در مجموعهٔ پیش‌فرض نیستند، پس یک اندپوینت فقط وقتی آن‌ها را دریافت می‌کند که نامشان را در eventTypes ببرد. اندپوینتی که به برخی نشانی‌ها محدود است فقط دربارهٔ فایل‌های همان نشانی‌ها خبر می‌گیرد، پس بارگذاری‌ای برای کل فضای کاری، با to برابر null، برایش فرستاده نمی‌شود.

form.submitted وقتی شلیک می‌کند که کسی از راه یکی از فرم‌های شما ثبت‌نام کند، و form.confirmed وقتی که یک ثبت‌نامِ در انتظار تأیید به گروه‌های مخاطبان بپیوندد، چون شخص پیوند تأیید را باز کرده یا شما آن را پذیرفته‌اید. form.submitted داده‌ای از نوع FormSubmittedEventData دارد: formId، formName، submissionId، email، status، answers، audienceIds، sourceUrl و submittedAt. form.confirmed داده‌ای از نوع FormConfirmedEventData دارد: formId، formName، submissionId، email، audienceIds، via که link یا approval است، و confirmedAt.

ثبت‌نام در فرمی بدون تأیید دوگانه، form.submitted را با status برابر added می‌فرستد و form.confirmed نمی‌فرستد، پس همین را لحظهٔ پیوستن کسی بدانید. کسی که پیش از تأیید دوباره ثبت‌نام کند همان submissionId را نگه می‌دارد، و form.submitted فقط وقتی دوباره فرستاده می‌شود که پاسخ‌هایش تغییر کرده باشد. رویدادهای فرم در مجموعهٔ پیش‌فرض نیستند، و اندپوینتی که به برخی نشانی‌ها محدود است هرگز آن‌ها را دریافت نمی‌کند، چون ثبت‌نام‌ها به کل فضای کاری تعلق دارند.

هر کدام از این شکل‌های داده یک TypedDict در openemail.types است. برای نمونه، رویداد تأییدشده را با نوع WebhookPayload[FileEventData] annotate کنید تا بررسی‌کنندهٔ نوع بداند event['data'] چه چیزی در خود دارد.

اثبات اینکه کار می‌کند

webhook_test.py
result = client.webhooks.test('whe_…')delivery = result['delivery'] if delivery is not None:    print(delivery['status'], delivery['responseCode']) for d in client.webhooks.iterate_deliveries('whe_…'):    print(d['eventType'], d['status'], d['responseCode'], d['error'])

یک responseCode برابر None یعنی اصلاً پاسخی وجود نداشته است (DNS، TLS، یک تایم‌اوت)، که واقعیتی متفاوت از پاسخی است که 0 گفته باشد. هر ردیف attempt و maxAttempts را با خود دارد، پس چند ردیف می‌توانند یک رویداد را توصیف کنند: eventId یکسان در میان آن‌ها همان رویداد است و شمارهٔ تلاش، همان کوشش. nextAttemptAt می‌گوید تلاش دوبارهٔ خودکارِ پس از یک ردیف کی موعدش می‌رسد.

فرستادن دوباره

webhook_replay.py
detail = client.webhooks.get_delivery('whe_…', 'whd_…')print(detail['payload'], detail['responseBody'], detail['replayRefusal']) replay = client.webhooks.replay_delivery('whe_…', 'whd_…')print(replay['delivery']['status'], replay['delivery']['responseCode'])

تحویلی که پیوسته شکست می‌خورد تا 8 بار تلاش می‌شود: همان لحظه، سپس پس از 1 دقیقه، 5 دقیقه، 30 دقیقه، 2 ساعت، 5 ساعت، 10 ساعت و 10 ساعت دیگر، روی هم حدود 27 ساعت و نیم. فقط شکستی تکرار می‌شود که ارزش تکرار داشته باشد: بی‌پاسخی، 408، 425، 429 یا یک 5xx. بازپخش، رویداد ذخیره‌شده را با همان id، type، createdAt و data دوباره می‌فرستد، پس گیرنده‌ای که شناسه‌های پردازش‌شده را کنار می‌گذارد آن را همان رویدادی می‌داند که می‌شناسد. فقط امضا تازه است.

  • replay_delivery یک رویداد را همین حالا می‌فرستد و پاسخی را که سرور شما داد برمی‌گرداند. روی تلاشی که تحویل شده هم کار می‌کند و هرگز دوباره تلاش نمی‌شود. پیش از فرستادن، تلاش‌های دوبارهٔ خودکارِ آن رویداد که هنوز شروع نشده‌اند متوقف می‌شوند: اگر بازپخش تحویل شود لغو می‌مانند، و اگر شکست بخورد طبق زمان‌بندی خود ادامه می‌یابند.
  • اگر در همان لحظه یک تلاش دوبارهٔ خودکار برای همان رویداد در حال فرستاده شدن باشد، replay_delivery چیزی نمی‌فرستد و با 409 retry_in_progress رد می‌شود، و تا وقتی بازپخش دیگری از آن هنوز در حال فرستاده شدن است با 409 replay_in_progress رد می‌شود، تا گیرندهٔ شما هرگز دو نسخه را هم‌زمان نگیرد، حتی از دو بازپخشی که در یک لحظه فرستاده شده‌اند. چند ثانیه صبر کنید و get_delivery را بخوانید، چون ممکن است همان تلاش یا بازپخش آن را تحویل دهد. بازپخش هر بار یک رویداد است: هیچ فراخوانی‌ای همهٔ تحویل‌های ناموفق را دوباره نمی‌فرستد.
  • همچنین با 409 اندپوینت خاموش (webhook_disabled)، رویدادی که اندپوینت دیگر به آن گوش نمی‌دهد (event_not_subscribed) یا دیگر پوشش نمی‌دهد (event_out_of_scope)، و تلاشی بدون رویداد ذخیره‌شده (delivery_not_replayable) را رد می‌کند. get_delivery همین پاسخ را از پیش در replayRefusal گزارش می‌دهد.

SDK هرگز replay_delivery را خودبه‌خود دوباره تلاش نمی‌کند، چون تلاش دوباره پس از پاسخی که گم شده رویداد را یک بار دیگر می‌فرستد.

هر رد شدن OpenEmailApiError را با status برابر 409، is_conflict برابر true و دلیل در code raise می‌کند، که یکی از مقدارهای WEBHOOK_REPLAY_ERROR_CODES است.

پارامترها: webhooks.create

urlstrالزامی
جایی که تحویل‌ها با POST به آن فرستاده می‌شوند. فقط HTTPS، و host نمی‌تواند `localhost`، نامی با پسوند `.localhost`/`.local`/`.internal`، یا یک IP لفظی loopback، خصوصی، CGNAT یا link-local باشد. این یک fetch سمت سرور به آدرسی است که شما می‌دهید، پس چنین مواردی روی `url` یک 422 هستند؛ بررسی، hostname را همان‌طور که نوشته شده می‌خواند و هرگز DNS را resolve نمی‌کند. آنچه ذخیره می‌شود سریال‌سازی تجزیه‌گر URL از چیزی است که فرستاده‌اید، پس `https://acme.com` به‌صورت `https://acme.com/` بازخوانده می‌شود.
eventTypeslist[WebhookEvent]
کدام رویدادها به این اندپوینت می‌رسند: هرکدام از نام‌های موجود در `WEBHOOK_EVENTS`. `POST /webhooks` آرایه را به تعداد رویدادهای موجود سقف می‌زند، پس یکی بیشتر از آن روی `eventTypes` یک 422 است؛ `PATCH` سقف نمی‌زند. تنها طول سقف دارد، و نام تکراری دقیقاً همان‌گونه که فرستاده‌اید ذخیره و بازخوانده می‌شود. نبودن یا خالی بودن به‌صورت فهرستی خالی ذخیره می‌شود، و به همین دلیل است که به‌صورت `['*']` بازخوانده می‌شود، و معنایش هر رویداد `email.*` جز `email.replied` است، امروز چهارده مورد، و هرگز خانواده‌های دامنه، suppression یا فایل. خانواده‌ای که بعداً افزوده شود هرگز به اندپوینتی که نامش را نبرده نمی‌رسد، پس یک یکپارچه‌سازی نمی‌تواند به‌خاطر یک انتشار شروع به دریافت شکلی کند که هرگز ندیده است.
descriptionstr
برچسبی برای اندپوینت، حداکثر 200 نویسه، تا فهرست وب‌هوک‌ها به‌صورت نام‌ها خوانده شود نه ستونی از URLها. اگر داده نشود، به‌صورت null ذخیره و بازگردانده می‌شود.

پاسخ: CreatedWebhookResource

objectLiteral['webhook']
همیشه `'webhook'`، همان تفکیک‌گری که یک خواندن ساده برمی‌گرداند، چون کلید مخفی یک کلید اضافه روی همان شکل معمول است نه یک نوع object جداگانه. اینکه `secret` حاضر باشد یا نه با متدی که صدا زده‌اید تعیین می‌شود، نه با این فیلد.
idstr
شناسهٔ اندپوینت: `whe_` و پس از آن 24 نویسهٔ hex. هر فراخوانی دیگر وب‌هوک آن را می‌گیرد: `get`، `update`، `delete`، `rotate_secret`، `test`، `list_deliveries`، `list_all_deliveries`، `iterate_deliveries`، `get_delivery` و `replay_delivery`.
urlstr
اندپوینت همان‌گونه که ذخیره شده است، پس از گذراندن بررسی‌های HTTPS و host مسدود. این همان URL تجزیه‌شده است که دوباره سریال شده، پس به‌جای رشته‌ای که فرستاده‌اید با این مقدار مقایسه کنید.
descriptionstr | None
برچسبی که داده‌اید، یا اگر چیزی نداده‌اید null. یک `update` که null صریح بفرستد آن را دوباره به null بازمی‌گرداند.
eventTypeslist[WebhookEvent] | ['*']
رویدادهای مشترک‌شده، یا وقتی اندپوینت هیچ‌کدام را نام نبرده باشد `['*']`. `['*']` شیوهٔ رندر شدن یک فهرست ذخیره‌شدهٔ خالی هنگام خواندن است و نمی‌توان آن را بازفرستاد، و نمایندهٔ چهارده رویداد پیام است نه کل فهرست. `create` و `update` تنها نام‌های واقعی رویدادها را می‌پذیرند.
enabledbool
اینکه تحویل‌ها تلاش می‌شوند یا نه؛ اندپوینت غیرفعال هنگام توزیع رویدادها رد می‌شود و کلید مخفی و تاریخچهٔ تحویلش را نگه می‌دارد. اینجا همیشه true است، چون `WebhookCreate` فیلد `enabled` ندارد و تنها `WebhookPatch` دارد.
lastDeliveryAtstr | None
مهر زمانی ISO 8601 آخرین تلاشِ تحویل، نه آخرین موفقیت. پس از یک POST ناموفق هم ثبت می‌شود، پس به شما می‌گوید اندپوینت آزموده شده است و `list_deliveries` می‌گوید چطور پیش رفت. تا نخستین تلاش null است، و بنابراین روی `create` همیشه null.
createdAtstr
مهر زمانی ISO 8601 زمانی که اندپوینت ثبت شده است. `list` اندپوینت‌ها را بر اساس همین فیلد از تازه‌ترین بازمی‌گرداند.
secretstr
کلید HMAC-SHA-256 که `X-OpenEmail-Signature` هر تحویل را امضا می‌کند: `whsec_` و پس از آن 32 بایت تصادفی به‌صورت base64url، و همان چیزی که به `verify_webhook_signature` می‌دهید. تنها `create` و `rotate_secret` آن را برمی‌گردانند و بس. خواندن هرگز آن را بازتاب نمی‌دهد، پس همین حالا ذخیره‌اش کنید؛ کلید مخفی گم‌شده را تنها با `rotate_secret` می‌توان جایگزین کرد، که قدیمی را بی‌درنگ باطل می‌کند.

فیلتر کردن گزارش‌ها

webhook_logs.py
from datetime import datetime, timedelta, timezone failed = client.webhooks.list_workspace_deliveries(    status='failed',    since=datetime.now(timezone.utc) - timedelta(days=1),)print(len(failed['items'])) history = client.webhooks.list_activity('whe_…')print([(change['type'], change['actor']['label'] if change['actor'] else None) for change in history['items']])

list_deliveries یک نقطهٔ پایانی را می‌خواند و list_workspace_deliveries همهٔ نقاط پایانی یا آن‌هایی را که endpoint_ids= نام می‌برد، و هر دو status=، since= و until= را می‌پذیرند، همان فیلترهای زبانهٔ تحویل‌ها در کنسول. list_activity و list_workspace_activity گزارش حسابرسی را می‌خوانند: چه کسی چه چیزی را ساخت، تغییر داد، خاموش یا روشن کرد، چرخاند، آزمود، دوباره فرستاد یا حذف کرد. کنار هرکدام یک list_all_… و یک iterate_… هست و هر ردیف گزارش فضای کاری endpointId دارد.

since= و until= یک datetime یا رشتهٔ ISO 8601 می‌گیرند. یک datetime از نوع naive به وقت محلی خوانده و به UTC تبدیل می‌شود، پس یک datetime از نوع aware بدهید، مثل نمونهٔ بالا.

مرجع