تخطَّ إلى المستندات
PHP

نقاط النهاية

`webhooks->list` و`listAll` و`iterate` و`get` و`create` و`update` و`delete` و`rotateSecret` و`test` و`getDelivery` و`replayDelivery`، وسجلات التسليم والنشاط.

كل الدوالّ

webhooks.php
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. ولا تعيده القراءة أبدًا، فخزّنه قبل أي شيء آخر. وأغفل eventTypes لتحصل على المجموعة الافتراضية، أي كل حدث email.* عدا email.replied. ولا تصل email.replied وdomain.* وsuppression.* وfile.* وform.* إلى نقطة نهاية إلا إذا سمّتها.

يعيد list صفحة OpenEmail\Result\Page واحدة، ويعيد listAll كل نقاط النهاية في مصفوفة واحدة، ويعيد iterate كائن Generator يسلّم نقطة نهاية واحدة في كل مرة. ويأخذ create وupdate المتن في صورة مصفوفة واحدة بأسماء API، وتعود كل نقطة نهاية في صورة مصفوفة مفاتيحها بصيغة camelCase.

ليس لـrotateSecret نافذة تداخل. فالسر القديم يتوقف عن العمل فورًا، فانشر الجديد قبل التدوير. ولا تُعاد محاولته تلقائيًا أبدًا: فإعادة المحاولة ستدوّر مرة ثانية وتبطل السر الذي أعادته المحاولة الأولى.

ولا تُعاد محاولة create أيضًا، فقد يترك فشل في الشبكة نقطة نهاية منشأة بسر لم تره قط. افحص list قبل أن تنشئها مجددًا. وتضم مساحة العمل 10 نقاط نهاية افتراضيًا، والتالية بعد بلوغ الحد تعطي 422 workspace_limit_reached.

ما يمكنك الاشتراك فيه

يسمّي OpenEmail\Constants\WebhookEvents كل حدث في صورة ثابت، ويسردها WebhookEvents::values()، فتستطيع عرض القائمة دون طلب. ويعيد webhooks->listEvents الأسماء نفسها مع تسمية لكل منها، إضافة إلى الحدود التي تلتزم بها نقطة النهاية في maxEndpoints وmaxAddresses وmaxDomains. والأحداث أحداث صندوق البريد لا أحداث هذه الواجهة: فـ 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 مجددًا إلا إذا تغيّرت إجاباته. وأحداث النماذج ليست في المجموعة الافتراضية، ونقطة النهاية المقيّدة ببعض العناوين لا تتلقاها أبدًا، لأن الاشتراكات تخص مساحة العمل كلها.

إثبات أنه يعمل

webhook_test.php
$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 اصطناعيًا موقَّعًا وينتظر انتهاء المحاولة. ويعود بشكل طبيعي أيًّا كان ما أجاب به مستقبِلك، فتفرّع على $result['delivery']['status']، لا على ما إذا كان الاستدعاء قد رمى استثناءً. والرمز 4xx جواب مفيد: فعنوان URL قابل للوصول والرفض جاء من معالجك أنت، وغالبًا من فحصه للتوقيع.

قيمة responseCode بـ null تعني أنه لم تكن هناك استجابة على الإطلاق (DNS، أو TLS، أو انتهاء مهلة)، وهي حقيقة مختلفة عن استجابة قالت 0. ويحمل كل صف attempt وmaxAttempts، فقد تصف عدة صفوف حدثًا واحدًا: فـ eventId المشترك بينها هو الحدث، ورقم المحاولة هو المحاولة. ويقول nextAttemptAt متى تحين إعادة المحاولة التلقائية بعد الصف.

إرساله مرة أخرى

webhook_replay.php
$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 مرات: فور حدوثه، ثم بعد دقيقة واحدة، و5 دقائق، و30 دقيقة، وساعتين، و5 ساعات، و10 ساعات، و10 ساعات أخرى، أي نحو 27 ساعة ونصف في المجموع. ولا يُكرَّر إلا الفشل الذي يستحق التكرار: لا استجابة، أو 408 أو 425 أو 429 أو 5xx. وإعادة التشغيل ترسل الحدث المخزَّن مرة أخرى بالقيم نفسها لـid وtype وcreatedAt وdata، فالمستقبِل الذي يُسقط المعرّفات التي عالجها من قبل يعامله على أنه الحدث الذي يعرفه. الجديد هو التوقيع وحده.

  • يرسل replayDelivery حدثًا واحدًا الآن ويعيد ما أجاب به خادمك. ويعمل على محاولة سُلِّمت أيضًا، ولا تُعاد محاولته أبدًا. وقبل أن يرسل، تُوقَف مؤقتًا إعادات المحاولة التلقائية لذلك الحدث التي لم تبدأ بعد: تبقى ملغاة إذا سُلِّمت إعادة التشغيل، وتُستأنف في موعدها إذا فشلت.
  • إذا كانت إعادة محاولة تلقائية للحدث نفسه تُرسَل في تلك اللحظة، فلا يرسل replayDelivery شيئًا ويرمي 409 retry_in_progress، وما دامت إعادة تشغيل أخرى له قيد الإرسال يرمي 409 replay_in_progress، فلا يتلقى مستقبِلك نسختين في الوقت نفسه أبدًا، حتى من إعادتي تشغيل أُرسلتا في اللحظة نفسها. انتظر بضع ثوانٍ واقرأ getDelivery، فقد تُسلِّم تلك المحاولة أو إعادة التشغيل الحدث. وإعادة التشغيل حدث واحد في كل مرة: لا يوجد استدعاء يعيد إرسال كل تسليم فاشل.
  • ويرمي أيضًا 409 لنقطة نهاية معطّلة (webhook_disabled)، أو لحدث لم تعد نقطة النهاية تستمع إليه (event_not_subscribed) أو لم تعد تغطيه (event_out_of_scope)، أو لمحاولة لا حدث مخزَّنًا لها (delivery_not_replayable). وكل منها ConflictException، ويسمّي OpenEmail\Constants\WebhookReplayErrorCodes الرموز. ويبلّغ getDelivery عن ذلك الجواب مسبقًا بوصفه replayRefusal، وقيمته null حين تمضي إعادة التشغيل، وإلا فمصفوفة فيها code وmessage.

لا تعيد الحزمة محاولة replayDelivery من تلقاء نفسها أبدًا، لأن إعادة المحاولة بعد استجابة ضائعة سترسل الحدث مجددًا.

المعاملات: webhooks->create

urlstringمطلوب
الوجهة التي تُرسَل إليها التسليمات بـ POST. بـ HTTPS حصرًا، ولا يجوز أن يكون المضيف `localhost` ولا اسمًا من نوع `.localhost` أو `.local` أو `.internal`، ولا عنوان IP حرفيًا من نوع loopback أو خاص أو carrier-grade 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 حرف، كي تُقرأ قائمة الـ webhooks كأسماء لا كعمود من عناوين 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` من عدمه يحدّده التابع الذي استدعيته، لا هذا الحقل.
idstring
معرّف نقطة النهاية: `whe_` متبوعًا بـ 24 حرفًا ست عشريًا. ويأخذه كل استدعاء webhook آخر: `get` و`update` و`delete` و`rotateSecret` و`test` و`listDeliveries` و`listAllDeliveries` و`iterateDeliveries` و`getDelivery` و`replayDelivery`.
urlstring
نقطة النهاية كما خُزّنت، بعد اجتياز فحص HTTPS وفحص المضيفات المحظورة. وهي عنوان URL المحلَّل بعد إعادة تسلسله، فقارن بهذه القيمة لا بالسلسلة النصية التي أرسلتها.
descriptionstring or null
التسمية التي أعطيتها لها، أو null إن لم تعطها تسمية. و`update` الذي يرسل `'description' => null` يمسحها.
eventTypesarray
الأحداث المشترَك فيها، أو `['*']` حين لم تسمِّ نقطة النهاية أي حدث. والقيمة `['*']` هي طريقة عرض قائمة مخزَّنة فارغة عند القراءة ولا يمكن إرسالها مجددًا، وهي تمثّل أحداث الرسائل الأربعة عشر لا الفهرس كله. ولا يقبل `create` و`update` إلا أسماء الأحداث الحرفية.
enabledbool
ما إذا كانت التسليمات تُحاوَل. فنقطة النهاية المعطّلة تُتخطى عند إرسال الأحداث وتحتفظ بسرّها وبسجل تسليماتها. وهي true دائمًا هنا، لأن `update` وحده يأخذ `enabled`.
disabledAtstring or null
متى عطّل الخادم نقطة النهاية بعد 100 تسليم فاشل متتالٍ. null ما دامت مفعّلة، وكذلك حين تكون قد عطّلتها بنفسك.
disabledReasonstring or null
لماذا عطّلها الخادم. null كلما كانت قيمة `disabledAt` هي null.
consecutiveFailuresint
التسليمات الفاشلة المتتالية. وأي حدث مُسلَّم يعيدها إلى 0، وكذلك يفعل `update` مع ضبط `enabled` على true.
addressAllowlistarray
العناوين المفردة التي تُبلَّغ عنها نقطة النهاية هذه.
domainAllowlistarray
النطاقات الكاملة التي تُبلَّغ عنها نقطة النهاية هذه. وخلوّ القائمتين يعني كل عنوان تملكه مساحة العمل.
lastDeliveryAtstring or null
طابع وقت بصيغة ISO 8601 لآخر محاولة تسليم، لا لآخر نجاح. ويُختم بعد طلب POST فاشل أيضًا، فهو يخبرك بأن نقطة النهاية جُرّبت، و`listDeliveries` يخبرك كيف سارت المحاولة. ويكون null حتى المحاولة الأولى، ولذلك هو null دائمًا في `create`.
createdAtstring
طابع وقت بصيغة ISO 8601 لوقت تسجيل نقطة النهاية. ويعيد `list` نقاط النهاية من الأحدث إلى الأقدم بحسب هذا الحقل.
secretstring
مفتاح HMAC-SHA-256 الذي يوقّع ترويسة `X-OpenEmail-Signature` في كل تسليم: `whsec_` متبوعًا بـ 43 حرفًا بترميز base64url، وهو ما تمرّره إلى `OpenEmail::verifyWebhookSignature`، مع البادئة. ويعيده `create` و`rotateSecret` ولا شيء غيرهما. ولا تعيده القراءة أبدًا، فخزّنه الآن. والسر الضائع لا يمكن استبداله إلا بـ `rotateSecret`، الذي يُبطل القديم فورًا.

ترشيح السجلات

webhook_logs.php
$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.