اندپوینتها
`webhooks->list`، `listAll`، `iterate`، `get`، `create`، `update`، `delete`، `rotateSecret`، `test`، `getDelivery` و `replayDelivery`، بههمراه گزارشهای تحویل و فعالیت.
همهٔ متدها
use OpenEmail\Constants\WebhookEvents; $endpoint = $client->webhooks->create([ 'url' => 'https://acme.com/hooks/mail', 'eventTypes' => [WebhookEvents::EMAIL_SENT, WebhookEvents::EMAIL_BOUNCED], 'description' => 'Billing service',]); file_put_contents('.openemail-webhook-secret', $endpoint['secret']); $client->webhooks->list();$client->webhooks->get($endpoint['id']);$client->webhooks->update($endpoint['id'], ['enabled' => false]);$client->webhooks->test($endpoint['id']); foreach ($client->webhooks->listDeliveries($endpoint['id'], limit: 1) as $latest) { $client->webhooks->getDelivery($endpoint['id'], $latest['id']); $client->webhooks->replayDelivery($endpoint['id'], $latest['id']);} $rotated = $client->webhooks->rotateSecret($endpoint['id']);file_put_contents('.openemail-webhook-secret', $rotated['secret']); $client->webhooks->delete($endpoint['id']);create جز rotateSecret تنها جایی است که کلید مخفی بازگردانده میشود. خواندن هرگز آن را بازتاب نمیدهد، پس پیش از هر کار دیگری ذخیرهاش کنید. برای مجموعهٔ پیشفرض، یعنی هر رویداد email.* جز email.replied، eventTypes را ندهید. email.replied، domain.*، suppression.*، file.* و form.* تنها وقتی به یک اندپوینت میرسند که آن اندپوینت نامشان را ببرد.
list یک OpenEmail\Result\Page برمیگرداند، listAll همهٔ اندپوینتها را در یک آرایه برمیگرداند، و iterate یک Generator برمیگرداند که هر بار یک اندپوینت را yield میکند. create و update بدنه را بهصورت یک آرایه با نامهای API میگیرند، و هر اندپوینت بهصورت یک آرایه با کلیدهای camelCase برمیگردد.
rotateSecret هیچ پنجرهٔ همپوشانی ندارد. کلید مخفی قدیمی بیدرنگ از کار میافتد، پس پیش از چرخاندن، کلید تازه را مستقر کنید. هرگز خودکار دوباره تلاش نمیشود: تلاش مجدد بار دوم میچرخاند و کلید مخفیای را که تلاش اول برگردانده بود باطل میکند.
create هم دوباره امتحان نمیشود، پس یک شکست شبکه ممکن است اندپوینتی ساختهشده با secretی که هرگز ندیدهاید باقی بگذارد. پیش از ساختن دوباره، list را بررسی کنید. هر فضای کاری بهطور پیشفرض 10 اندپوینت دارد، و اندپوینت بعدی پس از این سقف یک 422 workspace_limit_reached است.
چه چیزهایی را میتوان اشتراک گرفت
OpenEmail\Constants\WebhookEvents هر رویداد را بهصورت یک ثابت نام میبرد، و WebhookEvents::values() آنها را فهرست میکند، تا بتوانید فهرست را بدون درخواست نمایش دهید. webhooks->listEvents همان نامها را با یک برچسب برای هرکدام برمیگرداند، بهعلاوهٔ محدودیتهایی که اندپوینت به آنها مقید است، در maxEndpoints، maxAddresses و maxDomains. رویدادها، رویدادهای صندوق پستی هستند نه این API: email.received برای ایمیلی که در برنامه میرسد رخ میدهد، و email.sent برای پیامی که کامپوزر فرستاده است. مشترک شدن با تماشای ترافیک API خودتان یکی نیست.
file.uploaded وقتی رخ میدهد که فایلی در صفحهٔ «فایلها» گذاشته شود، و file.deleted وقتی فایلی حذف شود. data آنها شامل fileId، filename، mimeType، sizeBytes، direction، to، threadId، messageId، و uploadedAt یا deletedAt است. to نشانیای است که فایل به آن تعلق دارد، یا null برای فایلی که به کل فضای کاری تعلق دارد.
رویدادهای فایل در مجموعهٔ پیشفرض نیستند، پس یک اندپوینت فقط وقتی آنها را دریافت میکند که نامشان را در eventTypes ببرد. اندپوینتی که به برخی نشانیها محدود است فقط دربارهٔ فایلهای همان نشانیها خبر میگیرد، پس بارگذاریای برای کل فضای کاری، با to برابر null، برایش فرستاده نمیشود.
form.submitted وقتی رخ میدهد که کسی از راه یکی از فرمهای شما ثبتنام کند، و form.confirmed وقتی یک ثبتنامِ در انتظار به گروههای مخاطب میپیوندد، چون شخص پیوند تأیید را باز کرده یا شما آن را تأیید کردهاید. data در form.submitted شامل formId، formName، submissionId، email، status، answers، audienceIds، sourceUrl و submittedAt است. data در form.confirmed شامل formId، formName، submissionId، email، audienceIds، via، که link یا approval است، و confirmedAt است.
ثبتنام در فرمی بدون تأیید دوگانه، form.submitted را با status برابر added میفرستد و form.confirmed نمیفرستد، پس همین را لحظهٔ پیوستن کسی بدانید. کسی که پیش از تأیید دوباره ثبتنام کند همان submissionId را نگه میدارد، و form.submitted فقط وقتی دوباره فرستاده میشود که پاسخهایش تغییر کرده باشد. رویدادهای فرم در مجموعهٔ پیشفرض نیستند، و اندپوینتی که به برخی نشانیها محدود است هرگز آنها را دریافت نمیکند، چون ثبتنامها به کل فضای کاری تعلق دارند.
اثبات اینکه کار میکند
$result = $client->webhooks->test('whe_3f9c2a7b1e4d8f60a5c7b92d');echo $result['delivery']['status'], ' ', $result['delivery']['responseCode'] ?? 'no response', PHP_EOL; foreach ($client->webhooks->iterateDeliveries('whe_3f9c2a7b1e4d8f60a5c7b92d') as $delivery) { echo $delivery['eventType'], ' ', $delivery['status'], ' ', $delivery['responseCode'] ?? '-', ' ', $delivery['error'] ?? '', PHP_EOL;}test یک رویداد ساختگیِ امضاشدهٔ email.sent را POST میکند و منتظر میماند تا تلاش تمام شود. هر پاسخی که گیرندهٔ شما داده باشد، بهطور عادی برمیگردد، پس روی $result['delivery']['status'] شاخه بزنید، نه روی اینکه فراخوانی استثنا پرتاب کرده یا نه. یک 4xx پاسخ مفیدی است: URL در دسترس است و رد شدن از هندلر خودتان آمده، اغلب از بررسی امضای آن.
responseCode برابر null یعنی اصلاً پاسخی نیامده (DNS، TLS، پایان مهلت)، که واقعیتی متفاوت با پاسخی است که 0 گفته باشد. هر ردیف attempt و maxAttempts دارد، پس چند ردیف میتوانند یک رویداد را توصیف کنند: eventId یکسان در میان آنها همان رویداد است، و شمارهٔ تلاش همان دفعهٔ تلاش. nextAttemptAt میگوید تلاش دوبارهٔ خودکار پس از یک ردیف کی موعدش است.
فرستادن دوباره
$detail = $client->webhooks->getDelivery('whe_3f9c2a7b1e4d8f60a5c7b92d', 'whd_8c1e4a7f2b9d3e6a0c5f1b28');echo json_encode($detail['payload'], JSON_THROW_ON_ERROR), PHP_EOL;echo $detail['responseBody'] ?? 'no answer', ' ', $detail['replayRefusal']['code'] ?? 'replayable', PHP_EOL; $replay = $client->webhooks->replayDelivery('whe_3f9c2a7b1e4d8f60a5c7b92d', 'whd_8c1e4a7f2b9d3e6a0c5f1b28');echo $replay['delivery']['status'], ' ', $replay['delivery']['responseCode'] ?? 'no response', PHP_EOL;تحویلی که پیوسته شکست میخورد تا 8 بار تلاش میشود: همان لحظه، سپس پس از 1 دقیقه، 5 دقیقه، 30 دقیقه، 2 ساعت، 5 ساعت، 10 ساعت و 10 ساعت دیگر، روی هم حدود 27 ساعت و نیم. فقط شکستی تکرار میشود که ارزش تکرار داشته باشد: بیپاسخی، 408، 425، 429 یا یک 5xx. بازپخش، رویداد ذخیرهشده را با همان id، type، createdAt و data دوباره میفرستد، پس گیرندهای که شناسههای پردازششده را کنار میگذارد آن را همان رویدادی میداند که میشناسد. فقط امضا تازه است.
replayDeliveryیک رویداد را همین حالا میفرستد و آنچه سرور شما پاسخ داده را برمیگرداند. روی تلاشی که تحویل شده هم کار میکند و هرگز دوباره امتحان نمیشود. پیش از فرستادن، تلاشهای دوبارهٔ خودکار آن رویداد که هنوز آغاز نشدهاند متوقف میشوند: وقتی بازپخش تحویل شود لغوشده میمانند، و وقتی شکست بخورد طبق زمانبندی خودشان از سر گرفته میشوند.- اگر در همان لحظه یک تلاش دوبارهٔ خودکار از همان رویداد در حال فرستاده شدن باشد،
replayDeliveryچیزی نمیفرستد و یک 409retry_in_progressرا پرتاب میکند، و تا وقتی بازپخش دیگری از آن هنوز در حال فرستاده شدن است یک 409replay_in_progressرا پرتاب میکند، تا گیرندهٔ شما هرگز دو نسخه را همزمان نگیرد، حتی از دو بازپخش که در یک لحظه فرستاده شدهاند. چند ثانیه صبر کنید وgetDeliveryرا بخوانید، چون آن تلاش دوباره یا بازپخش ممکن است آن را تحویل دهد. بازپخش هر بار برای یک رویداد است: هیچ فراخوانیای همهٔ تحویلهای ناموفق را دوباره نمیفرستد. - همچنین برای اندپوینت خاموششده (
webhook_disabled)، رویدادی که اندپوینت دیگر به آن گوش نمیدهد (event_not_subscribed) یا دیگر آن را پوشش نمیدهد (event_out_of_scope)، و تلاشی بدون رویداد ذخیرهشده (delivery_not_replayable) یک 409 پرتاب میکند. هرکدام یکConflictExceptionاست، وOpenEmail\Constants\WebhookReplayErrorCodesکدها را نام میبرد.getDeliveryاین پاسخ را از پیش بهصورتreplayRefusalگزارش میکند، که اگر بازپخش انجام شود null است و در غیر این صورت آرایهای باcodeوmessage.
بسته هرگز خودش replayDelivery را دوباره امتحان نمیکند، چون تلاش دوباره پس از پاسخی گمشده رویداد را دوباره میفرستاد.
پارامترها: webhooks->create
urlstringالزامی- جایی که تحویلها به آن POST میشوند. فقط HTTPS، و میزبان نمیتواند `localhost`، نامی با `.localhost`، `.local` یا `.internal`، یا یک IP صریح از نوع loopback، خصوصی، NAT در سطح اپراتور، link-local، multicast یا unique local باشد. این درخواستی سمت سرور به نشانیای است که شما میدهید، پس اینها یک 422 `invalid_webhook_url` روی `url` هستند. بررسی نام میزبان را همانطور که نوشته شده میخواند، و هر تحویل میزبان را دوباره جستوجو میکند و از فرستادن به نشانیای در یکی از آن بازهها خودداری میکند. تحویلها هرگز هدایتها را دنبال نمیکنند، پس نشانی نهایی را ثبت کنید. آنچه ذخیره میشود سریالسازیِ تجزیهگر URL از چیزی است که فرستادهاید، پس `https://acme.com` به شکل `https://acme.com/` خوانده میشود.
eventTypesarray- کدام رویدادها به این اندپوینت میرسند: هر یک از مقدارهای `OpenEmail\Constants\WebhookEvents`. `create` اندازهٔ آرایه را به تعداد رویدادهای موجود محدود میکند، پس یکی بیشتر از آن یک 422 روی `eventTypes` است، و `update` آن را محدود نمیکند. فقط طول محدود میشود، و نام تکراری دقیقاً همانطور که فرستادهاید ذخیره و خوانده میشود. اگر ننویسید یا خالی باشد، بهصورت فهرستی خالی ذخیره میشود، و به همین دلیل به شکل `['*']` خوانده میشود، و یعنی همهٔ رویدادهای `email.*` جز `email.replied`، امروز چهارده رویداد، و هرگز خانوادههای دامنه، توقیف، فایل یا فرم. خانوادهای که بعدها افزوده شود هرگز به اندپوینتی که نامش را نبرده نمیرسد، پس یک یکپارچهسازی نمیتواند به خاطر یک انتشار، دریافت شکلی را آغاز کند که هرگز ندیده است.
descriptionstring- برچسبی برای اندپوینت، حداکثر 200 نویسه، تا فهرستی از وبهوکها بهصورت نامها خوانده شود نه ستونی از URLها. اگر ننویسید، بهصورت null ذخیره و برگردانده میشود. بهجای دادن null این کلید را ننویسید: کلاینت null را همانطور که هست میفرستد، و `create` آن را با یک 422 رد میکند.
addressAllowlistarray- نشانیهای تکی که این اندپوینت دربارهٔ آنها خبر میگیرد. رویدادی تحویل میشود که نشانی مربوط به آن در این فهرست باشد، یا دامنهاش در `domainAllowlist`. هر دو را خالی بگذارید تا اندپوینت دربارهٔ همهٔ نشانیهایی که فضای کاری مالک آنهاست خبر بگیرد. حداکثر 50، و نشانیای که این فضای کاری مالکش نیست یک 422 `invalid_parameter` است.
domainAllowlistarray- دامنههای کاملی که این اندپوینت دربارهٔ آنها خبر میگیرد، از جمله نشانیهایی که بعداً به آنها افزوده میشوند. یک دامنه رویدادهای `domain.*` خودش را هم میآورد. حداکثر 25.
apiKeystring- یک آرگومان نامدار در کنار آرایه، نه کلیدی درون آن: اندپوینت را بهجای کلید کلاینت با این کلید API میسازد.
پاسخ: اندپوینت ساختهشده
یک آرایه با کلیدهای camelCase. get، list و update همین شکل را بدون secret برمیگردانند.
objectstring- همیشه `webhook`، همان تمایزدهندهای که یک خواندن ساده برمیگرداند، چون secret یک کلید اضافه روی همان شکل معمولی است نه یک نوع شیء جداگانه. اینکه `secret` حاضر باشد یا نه را متدی که فراخواندهاید تعیین میکند، نه این فیلد.
idstring- شناسهٔ اندپوینت: `whe_` و به دنبالش 24 نویسهٔ hex. هر فراخوانی دیگر وبهوک آن را میگیرد: `get`، `update`، `delete`، `rotateSecret`، `test`، `listDeliveries`، `listAllDeliveries`، `iterateDeliveries`، `getDelivery` و `replayDelivery`.
urlstring- اندپوینت همانطور که ذخیره شده، پس از گذر از بررسیهای HTTPS و میزبانهای مسدود. همان URL تجزیهشده است که دوباره سریالسازی شده، پس با این مقدار مقایسه کنید نه با رشتهای که فرستادهاید.
descriptionstring or null- برچسبی که به آن دادهاید، یا null اگر چیزی نداده باشید. `update`ی که `'description' => null` بفرستد آن را پاک میکند.
eventTypesarray- رویدادهای مشترکشده، یا وقتی اندپوینت هیچکدام را نام نبرده باشد `['*']`. `['*']` شیوهٔ رندر شدن یک فهرست ذخیرهشدهٔ خالی هنگام خواندن است و نمیتوان آن را بازفرستاد، و نمایندهٔ چهارده رویداد پیام است نه کل فهرست. `create` و `update` تنها نامهای واقعی رویدادها را میپذیرند.
enabledbool- اینکه آیا تحویلها تلاش میشوند یا نه. اندپوینت غیرفعال هنگام روانه شدن رویدادها نادیده گرفته میشود و secret و تاریخچهٔ تحویلش را نگه میدارد. اینجا همیشه true است، چون فقط `update` مقدار `enabled` را میگیرد.
disabledAtstring or null- زمانی که سرور پس از 100 تحویل ناموفق پیاپی اندپوینت را خاموش کرد. تا وقتی روشن است null است، و همچنین وقتی خودتان آن را خاموش کردهاید.
disabledReasonstring or null- اینکه چرا سرور آن را خاموش کرد. هر وقت `disabledAt` برابر null باشد null است.
consecutiveFailuresint- تحویلهای ناموفق پیاپی. هر رویداد تحویلشده آن را به 0 بازمیگرداند، و `update` با `enabled` برابر true هم همینطور.
addressAllowlistarray- نشانیهای تکی که این اندپوینت دربارهٔ آنها خبر میگیرد.
domainAllowlistarray- دامنههای کاملی که این اندپوینت دربارهٔ آنها خبر میگیرد. خالی بودن هر دو فهرست یعنی همهٔ نشانیهایی که فضای کاری مالک آنهاست.
lastDeliveryAtstring or null- مهر زمانی ISO 8601 آخرین «تلاش» تحویل، نه آخرین موفقیت. پس از یک POST ناموفق هم ثبت میشود، پس به شما میگوید اندپوینت امتحان شده و `listDeliveries` میگوید نتیجه چه بوده. تا نخستین تلاش null است، و بنابراین روی `create` همیشه null است.
createdAtstring- مهر زمانی ISO 8601 زمانی که اندپوینت ثبت شده است. `list` اندپوینتها را بر اساس همین فیلد از تازهترین بازمیگرداند.
secretstring- کلید HMAC-SHA-256 که `X-OpenEmail-Signature` هر تحویل را امضا میکند: `whsec_` و به دنبالش 43 نویسهٔ base64url، و همان چیزی که با پیشوندش به `OpenEmail::verifyWebhookSignature` میدهید. فقط `create` و `rotateSecret` آن را برمیگردانند و هیچ چیز دیگری. هیچ خواندنی آن را بازنمیگرداند، پس همین حالا ذخیرهاش کنید. secret گمشده را فقط میتوان با `rotateSecret` جایگزین کرد، که secret قدیمی را بیدرنگ باطل میکند.
فیلتر کردن گزارشها
$failed = $client->webhooks->listWorkspaceDeliveries(status: 'failed', since: new \DateTimeImmutable('-1 day')); foreach ($failed as $delivery) { echo $delivery['endpointId'], ' ', $delivery['eventType'], ' ', $delivery['responseCode'] ?? '-', PHP_EOL;} $history = $client->webhooks->listActivity('whe_3f9c2a7b1e4d8f60a5c7b92d'); foreach ($history as $change) { echo $change['type'], ' ', $change['actor']['label'] ?? 'OpenEmail', PHP_EOL;}listDeliveries یک اندپوینت را میخواند و listWorkspaceDeliveries همهٔ اندپوینتها، یا آنهایی را که endpointIds: نام میبرد، بهصورت یک آرایه یا یک رشتهٔ جداشده با ویرگول، و هر دو status: (delivered یا failed)، since: و until: را میگیرند، یعنی فیلترهای زبانهٔ «تحویلها» در کنسول. listActivity و listWorkspaceActivity گزارش ممیزی را میخوانند: چه کسی چه چیزی را ساخت، تغییر داد، روشن یا خاموش کرد، چرخاند، آزمود، بازپخش کرد یا حذف کرد. هرکدام یک نسخهٔ listAll و یک نسخهٔ iterate در کنار خود دارند، مانند listAllDeliveries و iterateDeliveries، و هر ردیف گزارش فضای کاری endpointId دارد. webhooks->stats عددهای پشت زبانهٔ «تحلیل» را برای بازهای که انتخاب میکنید برمیگرداند.
since: و until: یک DateTimeInterface یا یک رشته با قالب ISO 8601 میگیرند، و رشتهای که فقط یک تاریخ دارد یعنی نیمهشب UTC همان روز. until: باید دیرتر از since: باشد، وگرنه فراخوانی یک InvalidRequestException پرتاب میکند که errorCode آن برابر invalid_parameter است.