توثيق الرابط

قبل أن ترسل علاقات أي حدث إلى خادمك، عليها أن تتأكد أن الرابط يخصك فعلاً. هذا ما يفعله التوثيق: ترسل علاقات طلب تحقق إلى رابطك، وتجيب أنت بتوقيع لا يستطيع إنتاجه إلا من يملك المفتاح السري للخطاف.

يجب توثيق خطاف الويب قبل أن يمكن تفعيله، وتعود حالته إلى غير موثق في كل مرة تُغيّر فيها رابطه.

طلب التحقق

عند الضغط على توثيق في بطاقة الخطاف (أو استدعاء نقطة نهاية التوثيق)، ترسل علاقات طلب GET إلى رابطك مع مُعاملين في الرابط:

GET /your-endpoint?payload=Xk3aQ&timestamp=1756600000 HTTP/1.1
Host: yourdomain.com
المُعامل الوصف
payload نص عشوائي من 5 خانات، يختلف في كل مرة.
timestamp الطابع الزمني (Unix) بالثواني للحظة إرسال طلب التحقق.

أمام رابطك 10 ثوانٍ للإجابة. والرد الأبطأ من ذلك يُعامل تماماً كالرد الخاطئ، ويبقى الخطاف غير موثق.

الرد المتوقع

اربط payload ثم timestamp بهذا الترتيب ودون أي فاصل بينهما، ووقّع الناتج بخوارزمية HMAC SHA-256 باستخدام المفتاح السري للخطاف، وأعد الناتج الست عشري بالأحرف الصغيرة كمحتوى الرد كاملاً:

signature = hmac_sha256(payload + timestamp, secret)

يجب أن يكون الرد:

  • برمز حالة ضمن 2xx،
  • ومحتواه التوقيع فقط — دون تغليف بـ JSON، ودون علامات اقتباس، ودون سطر جديد أو مسافات في نهايته.

تُقارن محتويات الرد بالتوقيع المتوقع حرفاً بحرف، لذا يكفي سطر جديد إضافي واحد ليفشل التحقق.

أمثلة

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

PHP

$secret = getenv('ALAAQAT_WEBHOOK_SECRET');

if ($_SERVER['REQUEST_METHOD'] === 'GET') {
    $signature = hash_hmac('sha256', $_GET['payload'].$_GET['timestamp'], $secret);

    header('Content-Type: text/plain');
    echo $signature; // دون سطر جديد
    exit;
}

Laravel

Route::get('/alaaqat-webhook', function (Request $request) {
    $signature = hash_hmac(
        'sha256',
        $request->query('payload').$request->query('timestamp'),
        config('services.alaaqat.webhook_secret')
    );

    return response($signature)->header('Content-Type', 'text/plain');
});

Node.js (Express)

const crypto = require('crypto');

app.get('/alaaqat-webhook', (req, res) => {
    const signature = crypto
        .createHmac('sha256', process.env.ALAAQAT_WEBHOOK_SECRET)
        .update(req.query.payload + req.query.timestamp)
        .digest('hex');

    res.type('text/plain').send(signature);
});

Python (Flask)

import hashlib
import hmac
import os

@app.get('/alaaqat-webhook')
def validate():
    message = request.args['payload'] + request.args['timestamp']
    signature = hmac.new(
        os.environ['ALAAQAT_WEBHOOK_SECRET'].encode(),
        message.encode(),
        hashlib.sha256,
    ).hexdigest()

    return signature, 200, {'Content-Type': 'text/plain'}

رابط واحد ونوعان من الطلبات

الرابط نفسه يستقبل طلب التحقق وإرسالات الأحداث معاً، لذا فرّق بينهما حسب طريقة الطلب:

  • GET ← طلب تحقق؛ أجب بالتوقيع.
  • POSTإرسالة حدث؛ أجب بأي رمز ضمن 2xx.

بعد نجاح التوثيق

تتحول حالة الخطاف إلى مُوَثَّق ويصبح زر تفعيل متاحاً. فعّله لتبدأ الأحداث المشترك بها بالوصول.

إذا فشل التوثيق

ترد علاقات برمز 403 والرسالة:

{
    "message": "Unable to validate the webhook. Please ensure that the secret key is correct and the webhook endpoint is properly configured."
}

تحقق حينها مما يلي:

  • أن الرابط متاح للوصول من الإنترنت — لن تعمل العناوين المحلية مثل localhost أو العناوين الخاصة. استخدم أداة نفق أثناء التطوير.
  • أن شهادة TLS صالحة وغير موقّعة ذاتياً.
  • ألا تكون هناك صفحة تسجيل دخول أو قائمة عناوين مسموحة أو مصادقة أساسية أو حماية من الروبوتات أمام الرابط.
  • أن الرابط يُرجع 200 وليس إعادة توجيه إلى صفحة أخرى.
  • أن الرد يصل خلال 10 ثوانٍ — فما بعدها يُعدّ فشلاً.
  • أن المحتوى هو التوقيع تماماً — انتبه للسطر الجديد في النهاية، أو التغليف بـ JSON مثل {"signature": "..."}، أو قالب HTML يلتف حوله.
  • أنك وقّعت payload متبوعاً مباشرة بـ timestamp، وباستخدام المفتاح السري لهذا الخطاف.
  • أنك تقرأ القيمتين من رابط الطلب الوارد وليس من محاولة سابقة محفوظة — فهما تتغيران في كل طلب تحقق.