خطافات الويب (Webhooks)
يُعلِم خطافُ الويب خادمَك عندما يحدث شيء يخص مؤسستك: يصل عرض سعر، أو يُموَّل حساب ضمان، أو تبلغ شحنة الميناء. ينشئ المالكون والمديرون خطافات الويب في لوحة التحكم، أو عبر POST /v1/orgs/{org_id}/webhooks.
ما تتلقاه
كل عملية تسليم هي طلب POST بجسم JSON صغير:
{
"id": "01a166eb-2f4e-74bd-a53e-6372d70ebead",
"type": "quote.sent",
"created_at": "2026-10-22T09:14:03.120000+00:00",
"data": {"conversation_id": "01a166e9-…", "quote_id": "01a166eb-…"}
}idهو معرّف الحدث. قد يصل الحدث نفسه أكثر من مرة؛ فعالِجه مرة واحدة فقط (خزّن المعرّفات التي عالجتها).- يحمل
dataمعرّفات فقط، ولا يحمل أبدًا نصوصًا أو أسماء أو مبالغ أو بيانات أشخاص. اقرأ الباقي بمفتاحorg:read، مثلGET /v1/org/quotes/{quote_id}. وبذلك تعمل دائمًا على الحالة الراهنة، حتى عندما تصل الأحداث متأخرة أو بغير ترتيبها. - يسرد دليل الأحداث كل نوع والمعرّفات التي يحملها. ولا يتلقى خطاف الويب إلا الأنواع التي طلبها.
الترويسات:
| الترويسة | القيمة |
|---|---|
Birlesa-Signature | t=<unix seconds>,v1=<hex signature> (قيمتان لـ v1 أثناء تدوير السرّ) |
Birlesa-Event | نوع الحدث |
Birlesa-Delivery | معرّف عملية التسليم هذه: تحتفظ به كل إعادات المحاولة للتسليم نفسه |
User-Agent | Birlesa-Webhooks/1 |
تحقّق من كل عملية تسليم
التوقيع هو HMAC-SHA256 محسوبًا على <t>.<raw body>. والمفتاح هو سرّ خطاف الويب كاملًا كما عُرض عليك (brl_whsec_...). تحقّق منه على البايتات الخام، قبل تحليل JSON. وارفض الطوابع الزمنية الأقدم من خمس دقائق، حتى لا يمكن إعادة إرسال عملية تسليم ملتقَطة في وقت لاحق.
import hashlib
import hmac
import time
TOLERANCE_SECONDS = 300
def verify(secret: str, body: bytes, header: str) -> bool:
fields = [part.split("=", 1) for part in header.split(",") if "=" in part]
stamps = [value for name, value in fields if name == "t"]
if len(stamps) != 1 or not stamps[0].isdigit():
return False
if abs(time.time() - int(stamps[0])) > TOLERANCE_SECONDS:
return False
expected = hmac.new(secret.encode(), f"{stamps[0]}.".encode() + body, hashlib.sha256)
return any(
hmac.compare_digest(expected.hexdigest(), value) for name, value in fields if name == "v1"
)import { createHmac, timingSafeEqual } from "node:crypto";
const TOLERANCE_SECONDS = 300;
export function verify(secret, rawBody, header) {
const fields = header.split(",").map((part) => part.split("=", 2));
const stamps = fields.filter(([name]) => name === "t").map(([, value]) => value);
if (stamps.length !== 1 || !/^\d+$/.test(stamps[0])) return false;
if (Math.abs(Date.now() / 1000 - Number(stamps[0])) > TOLERANCE_SECONDS) return false;
const expected = createHmac("sha256", secret).update(`${stamps[0]}.`).update(rawBody).digest();
return fields
.filter(([name]) => name === "v1")
.some(([, value]) => {
const given = Buffer.from(value ?? "", "hex");
return given.length === expected.length && timingSafeEqual(given, expected);
});
}أجِب بسرعة
أجِب بأي رمز 2xx خلال 10 ثوانٍ، ثم أنجز العمل. لا نقرأ جسم استجابتك ولا نتبع عمليات إعادة التوجيه: فالرمز 3xx يُحسب فشلًا.
إعادة المحاولة والإيقاف
عملية التسليم التي تفشل (لا إجابة، أو انتهاء المهلة، أو حالة ليست من فئة 2xx) تُعاد محاولتها بعد 1 و5 و15 دقيقة، ثم بعد 1 و3 و6 و12 ساعة، ثم مرتين أخريين بفارق يوم بينهما: عشر محاولات على مدى ثلاثة أيام تقريبًا. بعد ذلك تُعلَّم بأنها فاشلة؛ ويمكنك إرسالها مجددًا من سجل عمليات التسليم (POST .../deliveries/{id}/retry).
خطاف الويب الذي ظل يفشل ثلاثة أيام متتالية، بعشر محاولات على الأقل، يُوقَف (status: disabled، disabled_reason: failing) ويُتخلّى عمّا كان ينتظر التسليم. أصلِح نقطة الاستقبال، وأعِد تشغيله (PATCH مع {"active": true})، ثم أعِد محاولة عمليات التسليم التي تحتاجها.
العناوين التي نسلّم إليها
https://على المنفذ 443، تحت اسم مضيف عام. تُرفض عناوين IP، وlocalhost، والأسماء الداخلية (.local،.internal،.svc، ...) وبيانات الاعتماد داخل عنوان URL (webhook_url_refused؛ ويبيّنerrors[0].codeفي وثيقة المشكلة السبب:not_https،private_address، ...).- قبل كل عملية تسليم يُحَلّ الاسم من جديد. وإذا كان أيٌّ من عناوينه خاصًا، أو عنوان استرجاع ذاتي (loopback)، أو محليًا للرابط (link-local)، أو غير عام بأي شكل آخر، فلا يُرسَل شيء (
last_error: private_address). - يجب أن تكون الشهادة صالحة لاسم المضيف.
الأسرار
- يُعرض السرّ مرة واحدة، عند إنشاء خطاف الويب. ونحتفظ به مختومًا ولا يمكننا عرضه مرة أخرى.
- يعطي
POST .../rotateسرًّا جديدًا. ولمدة 24 ساعة تحمل عمليات التسليم توقيعين، بالسرّ الجديد والقديم معًا، كي تتمكن من نشر الجديد دون أن تفوتك أي عملية تسليم. ويُرفض تدوير ثانٍ خلال تلك الساعات الأربع والعشرين (rotation_pending).
الاختبار
يضع POST .../test عملية تسليم من نوع webhook.test في قائمة الانتظار (مع data فارغ). وتُرسَل كأي عملية أخرى، فتظهر نتيجتها في سجل عمليات التسليم (GET .../deliveries)، مع الحالة التي أجاب بها خادمك.
الحدود
لا يزيد عدد خطافات الويب على 10 لكل مؤسسة؛ ولا يزيد عدد نداءات الاختبار أو إعادات المحاولة اليدوية على 5 في الدقيقة. ويُحتفظ بسجل عمليات التسليم 30 يومًا.