Webhook الصادرة
باستخدام webhook صادرة يرسل exoCortex طلب POST إلى نظامكم في كل مرة يقع فيها حدث اشتركتم فيه: إنشاء تذكرة، أو تغيّر حالة، أو SLA يقترب من الانتهاء. وبذلك يتفاعل نظامكم فورًا، دون الحاجة إلى الاستعلام من API على فترات.
إنشاء اشتراك
Section titled “إنشاء اشتراك”- في exoCortex Core افتحوا Integrations → Webhooks. يلزم دور إداري.
- حدّدوا عنوان URL الذي سيستقبل الأحداث. يجب أن يكون
https://. - اختاروا الأحداث. تعني
*جميع الأحداث. - اختاروا الصيغة:
json: الصيغة القياسية لـ exoCortex، الموصوفة في هذه الصفحة؛datadog،pagerduty،slack-block: الصيغة الأصلية لتلك الخدمات، وتُستخدم عندما يكون عنوان URL تابعًا لها.
- إذا كانت البوابة لديكم تتطلب ذلك، أضيفوا ترويسات ثابتة، مثل ترويسة مصادقة.
- احفظوا. إذا لم تحدّدوا سرّ توقيع، يُنشئ exoCortex سرًّا (
whsec_…) ويعرضه مرة واحدة فقط: احفظوه، فأنتم بحاجة إليه للتحقق من التواقيع.
بإجراء الاختبار ترسلون إلى الاشتراك الحدث webhook.test وتتحققون من أن نظامكم يستقبله.
الأحداث
Section titled “الأحداث”| الحدث | متى يصل |
|---|---|
ticket.created |
أُنشئت تذكرة |
ticket.comment_added |
أُضيف تعليق |
ticket.field_changed |
تغيّر حقل |
ticket.assignee_changed |
تغيّر المُسنَد إليه |
ticket.status_changed |
تغيّرت الحالة |
ticket.priority_changed |
تغيّرت الأولوية |
ticket.sla_warning |
SLA على وشك الانتهاء |
ticket.sla_breach |
انتهت مهلة SLA |
ticket.resolved |
حُلّت التذكرة |
ticket.closed |
أُغلقت التذكرة |
ticket.reopened |
أُعيد فتح التذكرة |
ticket.link_created |
أُنشئ ربط بين تذاكر |
ticket.link_deleted |
أُزيل ربط بين تذاكر |
routing.no_match |
لم تعثر أي قاعدة توجيه على قائمة انتظار للتذكرة |
routing.manual_routed |
وجّه أحد الموظفين المختصين التذكرة يدويًا |
webhook.test |
أرسلتم حدثًا تجريبيًا إلى الاشتراك |
إضافةً إلى ذلك يمكنكم الاشتراك بالاسم في أحداث قواعد الإشعارات الخاصة بمستأجركم، مثل approval_requested.
قد يضيف exoCortex أنواعًا جديدة من الأحداث: يجب أن يتجاهل نظامكم الأنواع التي لا يعرفها.
صيغة الحدث
Section titled “صيغة الحدث”لكل حدث بصيغة json البنية نفسها:
{ "id": "evt_8a1f4c2e9b7d4e3f8a6b5c4d3e2f1a00", "type": "ticket.status_changed", "created_at": "2026-11-01T09:31:47.120Z", "api_version": "1", "tenant_id": "ACME", "data": { "ticket": { "key": "ACME-1042", "summary": "Payment gateway returns HTTP 502", "status": "In Progress", "priority": "High", "issuetype": "IT-INC", "assignee": "jane.doe", "created": "2026-11-01T09:14:05.000Z", "updated": "2026-11-01T09:31:47.000Z" }, "actor": "jane.doe", "changes": { "from": "Open", "to": "In Progress" } }}- يعرّف
idالحدث، وهو نفسه لجميع الاشتراكات التي تستقبله. actorهو المستخدم الذي تسبّب في الحدث، أوsystem.- يتضمن
changesتفاصيل الحدث: مثل الحالة السابقة والجديدة، أو نص التعليق.
حتى 30 يونيو 2027 يتضمن المتن أيضًا الحقول المُهمَلة event وtimestamp وsource، ونسخة من حقول data في المستوى الأول. يجب أن يقرأ أي نظام جديد الحقول الموصوفة أعلاه فقط.
الترويسات
Section titled “الترويسات”| الترويسة | مثال | المعنى |
|---|---|---|
X-Exo-Timestamp |
1793491200 |
لحظة التوقيع، بثواني Unix |
X-Exo-Signature |
v1=5257a869… |
توقيع الحدث |
X-Exo-Event |
ticket.created |
نوع الحدث، مطابق لـ type |
X-Exo-Delivery |
dlv_0b9d… |
معرّف فريد لمحاولة التسليم |
التحقق من التوقيع
Section titled “التحقق من التوقيع”قبل الوثوق بحدث، تحققوا من توقيعه:
- اقرؤوا المتن كما وصل، دون تحليل JSON وإعادة كتابته: فذلك سيغيّر البايتات.
- ارفضوا الطلب إذا كانت
X-Exo-Timestampمفقودة أو تختلف بأكثر من 300 ثانية عن ساعتكم. وبذلك تمنعون الطلبات المكررة. حافظوا على مزامنة الساعة عبر NTP. - احسبوا HMAC-SHA256 لـ
timestamp + "." + bodyبسرّ الاشتراك، بالنظام الست عشري بأحرف صغيرة. - قارنوا النتيجة، في زمن ثابت، بكل قيمة
v1=فيX-Exo-Signature. أثناء تدوير السر قد تحتوي الترويسة على أكثر من قيمة، مفصولة بفواصل.
مثال بلغة Node.js:
import { createHmac, timingSafeEqual } from 'node:crypto';
export function verifyExoWebhook(rawBody, headers, secret, toleranceSec = 300) { const ts = headers['x-exo-timestamp']; const sigHeader = headers['x-exo-signature'] || ''; if (!ts || Math.abs(Date.now() / 1000 - Number(ts)) > toleranceSec) return false; const expected = Buffer.from(createHmac('sha256', secret).update(`${ts}.${rawBody}`).digest('hex')); return sigHeader .split(',') .map((s) => s.trim()) .filter((s) => s.startsWith('v1=')) .some((s) => { const got = Buffer.from(s.slice(3)); return got.length === expected.length && timingSafeEqual(got, expected); });}مثال بلغة Python:
import hashlib, hmac, time
def verify_exo_webhook(raw_body: bytes, headers, secret: str, tolerance_sec: int = 300) -> bool: ts = headers.get('X-Exo-Timestamp') if not ts or abs(time.time() - int(ts)) > tolerance_sec: return False expected = hmac.new(secret.encode(), f"{ts}.".encode() + raw_body, hashlib.sha256).hexdigest() sigs = [s.strip()[3:] for s in headers.get('X-Exo-Signature', '').split(',') if s.strip().startswith('v1=')] return any(hmac.compare_digest(s, expected) for s in sigs)الترويسة X-NH-Signature، التي توقّع المتن فقط دون طابع زمني، مُهمَلة وستُزال في 30 يونيو 2027.
الرد ومعالجة النسخ المكررة
Section titled “الرد ومعالجة النسخ المكررة”- ردّوا بأي رمز
2xxخلال 5 ثوانٍ. إذا كان عليكم تنفيذ عمل طويل، فضعوه في قائمة انتظار وردّوا فورًا. - التسليم مضمون مرة واحدة على الأقل: قد يصل الحدث نفسه أكثر من مرة. استخدموا
idأوX-Exo-Deliveryللتعرّف على النسخ المكررة، مثلًا بالاحتفاظ بالمعرّفات التي وردت خلال آخر 5 دقائق. - قد تصل الأحداث بترتيب مختلف عن ترتيب وقوعها: رتّبوها باستخدام
created_at.