رُتبة CRM

الإشعارات الصادرة

استقبل أحداثًا موقعة من رُتبة CRM حين يصل عميل محتمل، أو يتأكد طلب، أو يُقبل عرض سعر، أو تُكسب فرصة.

تخبر الإشعارات الصادرة (webhooks) نظامك لحظة حدوث شيء في مساحة عملك، فلا تحتاج إلى سؤال الواجهة مرارًا. يُرسل كل حدث طلب POST بصيغة JSON وموقعًا إلى عنوان HTTPS تختاره. هذه الصفحة للمطور الذي يبني الطرف المستقبِل، أما إعدادها من الشاشة فمشروح أيضًا في الإشعارات الصادرة.

قبل أن تبدأ

  • الإشعارات الصادرة متاحة عندما تشمل باقتك واجهة API. ومن دونها لا تُسجَّل أحداث ولا يُرسل شيء.
  • المالكون والمسؤولون وحدهم يضيفون الإشعارات ويوقفونها ويشغلونها ويزيلونها، من الشاشة أو عبر الواجهة.
  • يجب أن يستخدم العنوان المستقبِل https على المنفذ المعتاد 443، دون اسم مستخدم أو كلمة مرور أو جزء #، وألا يزيد على 500 حرف. ويجب أن يشير اسم المضيف إلى عنوان IPv4 عام. والعناوين الداخلية والخاصة والمحجوزة لا يُتصل بها أبدًا.
  • للشركة حتى 5 عناوين، والموقوفة منها محسوبة، أما المزالة فلا.

الأحداث

الحدث اسمه على الشاشة يُرسل حين subject amount_minor
lead_created وصول عميل محتمل جديد تُنشأ جهة جديدة من نموذج موقع، أو من متجر مربوط، أو من أول رسالة في قناة مربوطة. ولا يُرسل للجهات المضافة يدويًا أو المستوردة أو المنشأة عبر الواجهة contact:CONTACT_ID null
order_confirmed تأكيد طلب متجر يبلغ طلب المتجر لأول مرة paid أو processing أو completed أو delivered. والطلبات الآتية من استيراد الطلبات السابقة لا تُرسل order:ORDER_ID مجموع الطلب
quote_accepted قبول عرض سعر يُقبل عرض سعر، من فريقك أو من العميل عبر رابط العرض quote:QUOTE_ID مجموع العرض
deal_won كسب فرصة تُعلَّم فرصة بأنها كُسبت deal:DEAL_ID مبلغ الفرصة، أو null

يُرسل كل حدث مرة واحدة على الأكثر لكل عنوان عن السجل نفسه. فالفرصة التي أُعيد فتحها ثم كُسبت مرة أخرى خلال 30 يومًا مثلًا لا تُرسل ثانية.

الاشتراك في إشعار

من الشاشة

  1. افتح جهات الاتصال، ثم أدوات، ثم مفاتيح API.
  2. اختر الإشعارات الصادرة.
  3. تحت إضافة عنوان اكتب عنوان HTTPS.
  4. تحت أرسل عند اختر حدثًا واحدًا أو أكثر.
  5. اختر إضافة عنوان.
  6. انسخ سر التوقيع من المربع الذي يقول انسخ سر التوقيع الآن، فلن يظهر مرة أخرى.

عبر الواجهة

تستطيع أدوات الأتمتة أن تشترك بنفسها بمفتاح صلاحيته قراءة وكتابة ومنشئه مالك أو مسؤول:

curl -X POST https://rutbacrm.com/api/v1/hooks \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Accept: application/json" \
  -H "Content-Type: application/json" \
  -d '{"url": "https://hooks.example.com/incoming", "events": ["lead_created", "deal_won"]}'

الرد 201 مع معرف الإشعار وسر توقيعه، ويظهر هذه المرة فقط:

{
  "id": "HOOK_ID",
  "secret": "YOUR_WEBHOOK_SECRET"
}

تُحذف أسماء الأحداث غير المعروفة. وإن لم يكن أي حدث معروفًا فالرد 422 والخطأ تحت events. والعنوان غير الصالح يرد 422 تحت url، وكذلك العنوان السادس.

ولإزالته أرسل DELETE بالمعرف. والترويسة Content-Type: application/json مطلوبة هنا أيضًا:

curl -X DELETE https://rutbacrm.com/api/v1/hooks/HOOK_ID \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Accept: application/json" \
  -H "Content-Type: application/json"
{
  "status": "removed"
}

يُزال العنوان فورًا: يختفي من شاشة الإشعارات الصادرة، ولا يُرسل إليه شيء بعد ذلك، ولا يعود محسوبًا ضمن العناوين الخمسة. وتُعلَّم الأحداث التي كانت تنتظره تُخطي. ولا يمكن تشغيل العنوان المزال من جديد، وإرسال DELETE له مرة أخرى يرد 404. ولاستخدام الرابط نفسه اشترك به من جديد، فيأخذ معرفًا جديدًا وسرًا جديدًا. ويستطيع المالك أو المسؤول أيضًا الضغط على إزالة في شاشة الإشعارات الصادرة، والأثر واحد.

الطلب

كل حدث طلب POST بجسم JSON وهذه الترويسات:

الترويسة القيمة
Content-Type application/json
X-Webhook-Event اسم الحدث، مثل deal_won
X-Webhook-Delivery معرف الإرسال، وهو نفسه في كل إعادة لهذا الحدث
X-Webhook-Signature t=TIMESTAMP,v1=SIGNATURE
{
  "id": "DELIVERY_ID",
  "event": "order_confirmed",
  "occurred_at": "2026-11-08T09:30:00Z",
  "data": {
    "contact": {
      "id": "CONTACT_ID",
      "name": "Maryam Saleh",
      "email": "maryam@example.com",
      "phone": "+966500000001"
    },
    "subject": "order:ORDER_ID",
    "amount_minor": 125050,
    "currency": "SAR"
  }
}
الحقل المعنى
id معرف الإرسال، وهو نفسه X-Webhook-Delivery
event اسم الحدث
occurred_at وقت وقوع الحدث بتوقيت UTC
data.contact الجهة التي يخصها الحدث، كما هي لحظة الإرسال. وقد يكون email وphone بقيمة null
data.subject ما يخصه الحدث بصيغة kind:ID. استخدمه مع معرف الجهة لتتعرف على السجل
data.amount_minor المبلغ بأصغر وحدة في العملة، فـ125050 تعني 1,250.50. وتكون null إن لم يوجد مبلغ
data.currency رمز العملة، أو null

تُرسل الأسماء حروفًا بترميز UTF-8 دون تهريب، فتصل الأسماء العربية نصًا عربيًا.

التحقق من التوقيع

كل طلب موقع بالسر الذي نسخته عند إضافة الإشعار. وللتحقق منه:

  1. قسّم X-Webhook-Signature عند الفواصل لتحصل على t وv1.
  2. اجمع t ثم نقطة ثم جسم الطلب الخام كما وصل، قبل أي تحليل لـJSON.
  3. احسب HMAC-SHA256 لهذا النص بالسر كاملًا مفتاحًا، بالصيغة الست عشرية بأحرف صغيرة.
  4. قارنه بـv1 مقارنة ثابتة الزمن، وارفض الطلب إن اختلفا.
  5. ارفض الطلب إن ابتعد t، وهو وقت Unix بالثواني، عن ساعتك بأكثر من بضع دقائق. وخمس دقائق نافذة معقولة.

t هو وقت هذه المحاولة، فإعادة الإرسال تحمل t جديدًا وتوقيعًا جديدًا.

PHP

<?php

$secret = getenv('WEBHOOK_SECRET');
$body = file_get_contents('php://input');
$header = $_SERVER['HTTP_X_WEBHOOK_SIGNATURE'] ?? '';

$parts = [];
foreach (explode(',', $header) as $piece) {
    [$name, $value] = array_pad(explode('=', $piece, 2), 2, '');
    $parts[$name] = $value;
}

$time = $parts['t'] ?? '';
$expected = hash_hmac('sha256', $time . '.' . $body, $secret);

if (! ctype_digit($time) || abs(time() - (int) $time) > 300 || ! hash_equals($expected, $parts['v1'] ?? '')) {
    http_response_code(401);
    exit;
}

$event = json_decode($body, true);
// Skip $event['id'] if you have already handled it, then do the work.
http_response_code(200);

JavaScript (Node.js مع Express)

const crypto = require('crypto');
const express = require('express');

const app = express();
const secret = process.env.WEBHOOK_SECRET;

app.post('/incoming', express.raw({ type: 'application/json' }), (req, res) => {
  const header = req.get('X-Webhook-Signature') || '';
  const parts = Object.fromEntries(header.split(',').map((piece) => piece.split('=')));
  const time = Number(parts.t);
  const expected = crypto.createHmac('sha256', secret).update(parts.t + '.').update(req.body).digest();
  const given = Buffer.from(parts.v1 || '', 'hex');
  const fresh = Number.isInteger(time) && Math.abs(Date.now() / 1000 - time) <= 300;

  if (!fresh || given.length !== expected.length || !crypto.timingSafeEqual(given, expected)) {
    return res.sendStatus(401);
  }

  const event = JSON.parse(req.body.toString('utf8'));
  // Skip event.id if you have already handled it, then do the work.
  res.sendStatus(200);
});

app.listen(3000);

أجب بسرعة

القاعدة القيمة
مهلة الاتصال 3 ثوانٍ
مهلة الطلب كاملًا 5 ثوانٍ
النجاح أي حالة 2xx، ويُتجاهل جسم الرد
التحويلات لا تُتبع، و3xx تُعد فشلًا

أجب بـ200 فور التحقق من التوقيع وحفظ الحدث، ثم أنجز العمل البطيء بعد ذلك. فأي رد يتجاوز 5 ثوانٍ فشل، ولو أكمل نظامك العمل.

إعادة المحاولة

ترسل الأحداثَ عمليةٌ تعمل كل دقيقة، وترسل في كل مرة حتى 50 حدثًا مستحقًا لكل شركة. والمحاولة الفاشلة تُعاد حتى 5 محاولات إجمالًا:

المحاولة متى
1 خلال دقيقة تقريبًا من الحدث
2 بعد دقيقة تقريبًا من المحاولة 1
3 بعد 5 دقائق تقريبًا من المحاولة 2
4 بعد 30 دقيقة تقريبًا من المحاولة 3
5 بعد ساعتين تقريبًا من المحاولة 4

انتهاء المهلة وخطأ الاتصال والحالة غير 2xx والتحويل والمضيف الذي صار يشير إلى عنوان داخلي، كلها محاولات فاشلة. وبعد الفشل الخامس يُعلَّم الحدث فشل ولا يُرسل مجددًا.

ولأن الحدث قد يصل أكثر من مرة، كأن يضيع ردك في الطريق، فاستخدم id لتعالج كل إرسال مرة واحدة. وقد تصل الأحداث بغير ترتيبها بعد الإعادة، فاعتمد على occurred_at لا على ترتيب الوصول.

الإيقاف التلقائي

حين تستنفد 10 أحداث متتالية كل محاولاتها دون نجاح واحد بينها، يُوقف العنوان ويظهر موقوف. والنجاح يعيد العد إلى الصفر، وكذلك الإيقاف أو التشغيل اليدوي.

وأثناء إيقاف العنوان لا تُسجَّل له الأحداث التي تقع أصلًا، ولا تُرسل إليه لاحقًا. أما الأحداث التي كانت منتظرة لحظة إيقافه فتبقى منتظرة، فاختر تشغيل في شاشة الإشعارات الصادرة لإرسالها. والأحداث التي تبقى منتظرة أكثر من 30 يومًا تُسقط وتُعلَّم تُخطي بدل إرسالها متأخرة.

سجل الإرسال

تعرض قائمة آخر الأحداث في شاشة الإشعارات الصادرة آخر 20 حدثًا عبر كل العناوين، مع حالتها ورمز الحالة الذي أعاده خادمك:

الحالة المعنى
بانتظار الإرسال في الطابور، أو ينتظر محاولته التالية
وصل أجاب خادمك بـ2xx
فشل فشلت المحاولات الخمس كلها
تُخطي لم يُرسل: مُحيت الجهة قبل الإرسال، أو انتظر الحدث أكثر من 30 يومًا

تُحفظ سجلات الإرسال 30 يومًا. ولا يخزن السجل بيانات الجهة أبدًا، فهي تُقرأ لحظة الإرسال، ولهذا لا تُرسل جهة ممحوة أبدًا.

معلومات مفيدة

  • لا يمكن رؤية سر التوقيع ولا تغييره بعد ظهوره. ولاستخدام سر جديد أضف العنوان مرة أخرى، وحدّث نظامك بالسر الجديد، ثم أزل المدخل القديم. وإلى أن تزيله يُحسب المدخلان ضمن العناوين الخمسة.
  • لا يوجد زر للتجربة. أطلق حدثًا حقيقيًا، كأن تعلّم فرصة تجريبية بأنها كُسبت، وراقب آخر الأحداث.
  • إن فقدت الباقة واجهة API فالأحداث التي تقع خلال ذلك لا تُسجَّل أصلًا، والمنتظرة لا تُرسل حتى تعود الميزة.

حل المشكلات

المشكلة ما تتحقق منه
لا يصل شيء والسجل يُظهر بانتظار الإرسال العنوان موقوف، أو الباقة لا تشمل واجهة API. وقد يستغرق خروج الحدث دقيقة
فشل دون رمز حالة فشل الاتصال أو انتهت مهلته، أو المضيف يشير إلى عنوان خاص أو إلى IPv6 فقط
فشل مع 301 أو 302 عنوانك يحوّل إلى غيره. استخدم العنوان النهائي
التوقيع لا يتطابق أبدًا توقّع JSON بعد تحليله وإعادة ترميزه. استخدم الجسم الخام والسر كاملًا مفتاحًا
العنوان يظهر موقوف فشلت 10 أحداث متتالية. أصلح المستقبِل ثم اختر تشغيل

آخر تحديث 2026-11-08