ahlan hamad

الويب هوك

يُطلق في v1.2. العقد منشور حتى تبني عليه الآن؛ نقاط نهاية الويب هوك لم تُطلق بعد. تابع سجل التغييرات.

الويب هوك يخبر نظامك بأن شيئاً تغيّر — موظف أُضيف، أو مسيّر رواتب أُغلق — بدلاً من أن تسأل الـ API باستمرار. كل إرسال موقّع بسرّ نقطة النهاية.

سجّل نقطة نهاية

تحتاج صلاحية webhooks:manage. الرابط يجب أن يكون https:// على مضيف عام؛ العناوين الخاصة وعناوين الحلقة المحلية مرفوضة. الاستجابة تتضمن secret (يبدأ بـ whsec_) مرة واحدة فقط — احفظه. لتدوير السرّ احذف نقطة النهاية وأنشئها من جديد.

curl -X POST https://actions.ahlanhamad.com/v1/webhooks \
  -H "Authorization: Bearer $AH_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://yourapp.example/webhooks/ahlan-hamad",
    "events": ["employee.updated", "payroll_run.finalized"]
  }'

الأحداث

الحدثمتى
employee.createdأُضيف موظف
employee.updatedتغيّر حقل يراه الشريك في بيانات موظف
employee.leftغادر موظف
payroll_run.finalizedأُغلق مسيّر رواتب ورُحّل قيده
payroll_run.paidعُلِّم مسيّر رواتب كمدفوع ورُحّل قيد الدفع
payroll_run.correctedتغيّرت مبالغ مسيّر مغلق — اقرأه من جديد
payroll_run.deletedحُذف مسيّر نهائي وعُكس قيده
attendance.rejectedحركة حضور مخزّنة لم تتحول إلى حضور
pingحدث تجريبي

شكل الإرسال

POST /webhooks/ahlan-hamad HTTP/1.1
Content-Type: application/json
X-AH-Signature: t=1759132931,v1=5f2b…

{
  "id": "evt_…",
  "type": "payroll_run.finalized",
  "created_at": "2026-09-29T08:02:11Z",
  "company_id": "k57f…",
  "livemode": false,
  "data": { … }
}

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

كل إرسال يحمل الترويسة X-AH-Signature: t=<unix seconds>,v1=<hex>، حيث v1 = HMAC-SHA256(secret, "<t>.<raw body>"). تحقق على البايتات الخام للجسم قبل تحليل JSON، وارفض الإرسال إذا لم يطابق التوقيع أو إذا ابتعد t عن ساعتك أكثر من 5 دقائق. استخدم مقارنة ثابتة الزمن.

Node.js

import crypto from "node:crypto";

// rawBody: the exact bytes we sent, as a string — read it BEFORE JSON.parse
// (in Express: app.post(path, express.raw({ type: "application/json" }), …)).
export function verifyAhSignature(rawBody, header, secret, toleranceSec = 300) {
  const parts = Object.fromEntries(header.split(",").map((p) => p.split("=")));
  const t = Number(parts.t);
  if (!t || Math.abs(Date.now() / 1000 - t) > toleranceSec) return false;
  const expected = crypto
    .createHmac("sha256", secret)
    .update(`${t}.${rawBody}`)
    .digest("hex");
  const given = Buffer.from(parts.v1 ?? "", "hex");
  const want = Buffer.from(expected, "hex");
  return given.length === want.length && crypto.timingSafeEqual(given, want);
}

Python

import hashlib, hmac, time

def verify_ah_signature(raw_body: bytes, header: str, secret: str, tolerance: int = 300) -> bool:
    parts = dict(p.split("=", 1) for p in header.split(","))
    t = int(parts.get("t", "0"))
    if abs(time.time() - t) > tolerance:
        return False
    expected = hmac.new(secret.encode(), f"{t}.".encode() + raw_body, hashlib.sha256).hexdigest()
    return hmac.compare_digest(expected, parts.get("v1", ""))

PHP

function verify_ah_signature(string $rawBody, string $header, string $secret, int $tolerance = 300): bool {
    parse_str(str_replace(',', '&', $header), $parts);
    $t = (int)($parts['t'] ?? 0);
    if (abs(time() - $t) > $tolerance) return false;
    $expected = hash_hmac('sha256', $t . '.' . $rawBody, $secret);
    return hash_equals($expected, $parts['v1'] ?? '');
}

// $rawBody = file_get_contents('php://input');
// $header  = $_SERVER['HTTP_X_AH_SIGNATURE'] ?? '';

التسليم وإعادة المحاولة

المحاولة234567
بعد الفشل السابق بـدقيقة5 دقائق30 دقيقةساعتان12 ساعة24 ساعة

اختبر وصحّح بنفسك