خطاف الويب (Webhook) يتيح لعلاقات إرسال البيانات إلى خادمك الخاص لحظة حدوث أي تغيير في حسابك — إنشاء جهة اتصال، تعديل صفقة، حذف ملاحظة — دون الحاجة إلى استعلام دوري عن التغييرات عبر واجهة التطبيق البرمجية.
كل خطاف ويب يتبع حساباً واحداً، ويشير إلى رابط HTTPS واحد لديك، ويشترك في قائمة الأحداث التي تختارها.
| المتطلب | التفاصيل |
|---|---|
| الخطة | خطافات الويب متاحة في خطة الأعمال (Business) أو في خطة مخصصة تشملها. لا يمكن إنشاؤها في الخطة المجانية أو الخطة الأساسية. |
| العدد المسموح | حتى 5 خطافات ويب لكل حساب في خطة الأعمال. |
| الرابط | رابط HTTPS يمكن الوصول إليه من الإنترنت. الروابط التي تبدأ بـ http:// مرفوضة. |
| الصلاحيات | يحتاج عضو الفريق إلى صلاحيات webhook-index و webhook-create و webhook-update و webhook-delete. |
يمر خطاف الويب بثلاث خطوات، ولا يُرسل أي شيء قبل اكتمالها جميعاً.
{info} تعديل رابط خطاف موجود يعيد حالته إلى غير موثق، وتتوقف الإرسالات حتى توثقه من جديد.
https://) واختر الأحداث التي تريد استقبالها.يظهر الخطاف الجديد في بطاقة تعرض الرابط، والمفتاح السري (مع زر نسخ)، وحالتَي التوثيق والتفعيل، والأحداث المشترك بها. ومن البطاقة نفسها يمكنك التوثيق والتفعيل أو إلغاء التفعيل والتعديل والحذف.
ويمكنك تنفيذ ذلك كله عبر واجهة التطبيق البرمجية — راجع إدارة خطافات الويب.
{danger.fa-close} عامل المفتاح السري ككلمة المرور، فمن يملكه يستطيع اجتياز طلب التحقق بالنيابة عنك.
عند وقوع حدث مشترك به، ترسل علاقات طلب POST بمحتوى JSON إلى رابطك:
POST /your-endpoint HTTP/1.1
Host: yourdomain.com
Content-Type: application/json
X-Alaaqat-Event: contact-created
X-Alaaqat-Delivery: 6f9d1c2a-1f36-4a1c-9a4e-6a3f0c2b7d11
X-Alaaqat-Timestamp: 1756600000
X-Alaaqat-Signature: sha256=6b8f0d0e2f1a7c4d9b3e5a1c8f2d4e6a0b9c7d5e3f1a2b4c6d8e0f2a4b6c8d0e
{
"time": 1756600000.123456,
"contact": {
"properties": {
"account_id": 1,
"firstname": "test",
"lastname": "test",
"email": "test@alaaqat.com",
"updated_at": "2024-05-27T22:34:23.357000Z",
"created_at": "2024-05-27T22:34:23.357000Z",
"_id": "66550a6f014742f5f101b824",
"fullname": "test test",
"image": "https://ui-avatars.com/api/?rounded=true&bold=true&name=test test"
},
"channels": []
},
"event": "contact-created"
}
يحتوي المحتوى دائماً على ثلاثة مفاتيح:
| المفتاح | الوصف |
|---|---|
time |
الطابع الزمني (Unix) بالميكروثانية للحظة إضافة الحدث إلى الطابور. |
event |
اسم الحدث، مثل contact-created. |
| الكائن | السجل المتأثر. اسم المفتاح مشتق من اسم الكائن — contact أو deal_note أو ticket_property … — ومحتواه بنفس شكل ما تُرجعه واجهة التطبيق البرمجية لذلك الكائن. |
اسم مفتاح الكائن يتبع اسم الحدث:
| الحدث | مفتاح الكائن |
|---|---|
contact-created |
contact |
contact-property-updated |
contact_property |
contact-property-group-deleted |
contact_property_group |
contact-note-created |
contact_note |
وتنطبق القاعدة نفسها على بقية الكائنات (deal و ticket و company و invoice و product).
تحمل كل إرسالة أربع ترويسات خاصة بنا إضافة إلى Content-Type: application/json:
| الترويسة | الوصف |
|---|---|
X-Alaaqat-Event |
اسم الحدث، وهو نفس قيمة المفتاح event في المحتوى. |
X-Alaaqat-Delivery |
معرّف UUID لهذه الإرسالة، يبقى ثابتاً في كل إعادة محاولة — استخدمه مفتاحاً لمنع التكرار. |
X-Alaaqat-Timestamp |
الطابع الزمني (Unix) لهذه المحاولة. يُولَّد من جديد مع كل إعادة محاولة، لذا يبقى حديثاً دائماً. |
X-Alaaqat-Signature |
sha256= متبوعاً بتوقيع HMAC بصيغة hex، راجع قسم التحقق من الإرسالة أدناه. |
أجب بأي رمز حالة ضمن 2xx وبأسرع ما يمكن؛ محتوى الرد يُتجاهل. تنتهي مهلة الطلب بعد 30 ثانية.
استقبل الطلب وأجب فوراً، ونفّذ المعالجة الفعلية في طابور خلفي لديك — فالرابط البطيء تُعاد محاولته كما لو أنه فشل.
إذا اشترك أكثر من خطاف في الحساب بالحدث نفسه، فسيتلقى كل واحد منها طلب POST مستقلاً. ويُرسَل إلى كل خطاف على حدة، لذا لا يؤثر فشل أحد الروابط على البقية.
أي رد خارج نطاق 2xx — رمز خطأ أو تجاوز للمهلة أو تعذّر الاتصال — تُعاد محاولته. تحصل كل إرسالة على 6 محاولات موزّعة على نحو يوم كامل:
| المحاولة | موعد الإرسال |
|---|---|
| 1 | فوراً |
| 2 | بعد 5 دقائق |
| 3 | بعد 30 دقيقة من سابقتها |
| 4 | بعد ساعة من سابقتها |
| 5 | بعد 3 ساعات من سابقتها |
| 6 | بعد يوم من سابقتها |
لذلك لا يكلّفك النشر أو إعادة التشغيل أو انقطاع بضع ساعات لديك أي شيء: تصلك الأحداث فور عودتك.
{warning} تعني إعادة المحاولة أن الحدث نفسه قد يصلك أكثر من مرة — مثلاً حين ينفّذ خادمك العمل ثم يضيع الرد. قيمة
X-Alaaqat-Deliveryواحدة في كل محاولات الإرسالة نفسها، فاحفظها وتجاهل أي معرّف إرسالة سبق أن عالجته.
إذا استنفدت 3 إرسالات متتالية محاولاتها الست — أي ما يقارب ثلاثة أيام من رابط معطّل — تضبط علاقات الخطافَ على غير مفعّل وتتوقف عن الإرسال إليه. ويصل كل عضو نشط في الحساب يملك صلاحية webhook-update إشعارٌ بالبريد الإلكتروني وداخل التطبيق يذكر الرابط الذي أُلغي تفعيله.
وهذا الإشعار هو إلغاء تفعيل خطاف الويب، فيختار كل عضو القنوات التي يصله عبرها — المحفوظة أو المنبثقة أو إشعارات المتصفح أو البريد الإلكتروني — أو يوقفه تماماً، من الإعدادات > التفضيلات > الإشعارات. وجميع القنوات مفعّلة افتراضياً، ويُستحسن إبقاء البريد الإلكتروني مفعّلاً: فتعطّل الرابط يعني غالباً أن لا أحد يتابع لوحة التحكم أيضاً.
يبقى الخطاف موثقاً، لذا تكفي عملية تفعيل واحدة (أو زر تفعيل في بطاقة الخطاف) لإعادته إلى العمل بعد إصلاح الرابط. ويصفّر التفعيل عدّاد حالات الفشل، وكذلك يفعل تغيير الرابط، فيبدأ الخطاف من جديد بدل أن يكون على بُعد إرسالة فاشلة واحدة من إلغاء التفعيل مرة أخرى. وأي إرسالة ناجحة تصفّر العدّاد أيضاً، فلا تتراكم حالات الفشل المتفرقة حتى تصل إلى إلغاء التفعيل.
تُوقَّع كل إرسالة بـالمفتاح السري للخطاف، ما يتيح لك إثبات أن الطلب صادر فعلاً عن علاقات وأنه لم يُعدَّل في الطريق.
التوقيع هو HMAC-SHA256 على النص "{timestamp}:{raw body}" باستخدام المفتاح السري:
X-Alaaqat-Signature: sha256=<hex digest of hash_hmac('sha256', timestamp + ':' + rawBody, secret)>
وهناك قاعدتان مهمتان:
hash_equals أو crypto.timingSafeEqual أو hmac.compare_digest) ولا تستخدم == أبداً.أعد الرمز 401 عند عدم تطابق التوقيع. وارفض أيضاً أي إرسالة يتجاوز عمر X-Alaaqat-Timestamp فيها بضع دقائق — فالطابع الزمني يُولَّد من جديد مع كل محاولة، لذا تحمل آخر محاولة في إرسالة عمرها يوم طابعاً حديثاً.
PHP
$body = file_get_contents('php://input');
$timestamp = $_SERVER['HTTP_X_ALAAQAT_TIMESTAMP'] ?? '';
$signature = $_SERVER['HTTP_X_ALAAQAT_SIGNATURE'] ?? '';
$expected = 'sha256=' . hash_hmac('sha256', $timestamp . ':' . $body, $secret);
if (!hash_equals($expected, $signature) || abs(time() - (int) $timestamp) > 300) {
http_response_code(401);
exit;
}
Node.js (Express مع الاحتفاظ بالمحتوى الخام)
const crypto = require('crypto');
app.post('/alaaqat-webhook', express.raw({ type: 'application/json' }), (req, res) => {
const timestamp = req.get('X-Alaaqat-Timestamp') || '';
const signature = req.get('X-Alaaqat-Signature') || '';
const expected = 'sha256=' + crypto
.createHmac('sha256', secret)
.update(`${timestamp}:${req.body.toString('utf8')}`)
.digest('hex');
const ok = expected.length === signature.length
&& crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(signature))
&& Math.abs(Date.now() / 1000 - Number(timestamp)) < 300;
if (!ok) {
return res.sendStatus(401);
}
res.sendStatus(200);
});
Python (Flask)
import hashlib, hmac, time
from flask import request, abort
@app.post('/alaaqat-webhook')
def alaaqat_webhook():
body = request.get_data()
timestamp = request.headers.get('X-Alaaqat-Timestamp', '')
signature = request.headers.get('X-Alaaqat-Signature', '')
expected = 'sha256=' + hmac.new(
secret.encode(),
f'{timestamp}:'.encode() + body,
hashlib.sha256,
).hexdigest()
if not hmac.compare_digest(expected, signature) or abs(time.time() - int(timestamp or 0)) > 300:
abort(401)
return '', 200
{info} المفتاح السري هو نفسه المستخدم في عملية التوثيق، ويمكنك قراءته في أي وقت من بطاقة الخطاف أو من نقطة السرد.
تُصدر علاقات الأحداث الاثني عشر نفسها لكل من الكائنات الستة: contact و company و deal و ticket و invoice و product.
خذ اسم الكائن وأضف إليه إحدى اللواحق التالية لتحصل على اسم الحدث — deal-created و invoice-note-updated و product-property-group-deleted وهكذا.
| اللاحقة | تُطلق عند |
|---|---|
-created |
إنشاء السجل |
-updated |
تعديل السجل |
-deleted |
حذف السجل |
-property-created |
إنشاء خاصية مخصصة |
-property-updated |
تعديل خاصية مخصصة |
-property-deleted |
حذف خاصية مخصصة |
-property-group-created |
إنشاء مجموعة خصائص |
-property-group-updated |
تعديل مجموعة خصائص |
-property-group-deleted |
حذف مجموعة خصائص |
-note-created |
إنشاء ملاحظة |
-note-updated |
تعديل ملاحظة |
-note-deleted |
حذف ملاحظة |
على سبيل المثال، القائمة الكاملة لجهات الاتصال هي:
contact-created
contact-updated
contact-deleted
contact-property-created
contact-property-updated
contact-property-deleted
contact-property-group-created
contact-property-group-updated
contact-property-group-deleted
contact-note-created
contact-note-updated
contact-note-deleted
{info} تُصدر الأحداث أياً كان مصدر التغيير — لوحة التحكم أو الاستيراد أو واجهة التطبيق البرمجية.