الأدلة الإرشادية
Webhooks
تتلقى نقطة نهاية webhook ما توجّهه إليها قواعد تنبيه مساحة العمل: إصدار محجوب، أو نتيجة تنتظر شخصًا، أو تشغيل مُدار انتهى، أو إنذار مراقب إنتاج. كل تسليم جسم JSON موقَّع بسر نقطة النهاية، ليتمكن خادمك من التحقق من أنه جاء من مساحة عملك قبل التصرف بناءً عليه.
إضافة نقطة نهاية
في صفحة التكاملات لمساحة العمل، يضيف Owner أو Admin webhook موقَّعًا باسم وعنوان URL عام بـ https على المنفذ 443 أو 8443. تعرض الصفحة بعد ذلك سر توقيع نقطة النهاية، الذي يبدأ بـ whsec_. انسخه حينها: فهو يُخزَّن مشفَّرًا ولا يُعرَض مرة أخرى. لاستبداله، احذف نقطة النهاية وأضفها من جديد.
ثم، في صفحة التنبيهات، اختر نقطة النهاية قناةً لقاعدة أو أكثر. يرسل زر "إرسال اختبار" تسليمًا من نوع oloproof.test ويعرض الحالة التي أجاب بها خادمك.
ما يُرسَل
{
"type": "oloproof.gate_blocked",
"id": "dlv_8c1f0a2b3c4d5e6f7a8b9c0d",
"created_at": "2026-10-26T14:03:00.000Z",
"workspace": "acme",
"rule": { "id": "rul_1a2b3c4d5e6f7a8b9c0d1e2f", "name": "Do not ship" },
"event": "gate_blocked",
"items": [
{
"project": "support",
"kind": "run",
"id": "run_20261026T140100_4f2a",
"release_action": "BLOCK",
"rules": [["faithfulness", "FAIL"]],
"href": "https://oloproof.com/app/workspaces/acme/projects/support/runs/run_20261026T140100_4f2a"
}
],
"more": 0
}يحمل العنصر المعرّفات، وحالات قرار المحرك، وإجراء الإصدار، وحالة المهمة ونتيجتها، ومستوى المراقب وعتبته وفترته كما كتبها المحرك، والروابط. ولا يحمل أبدًا مدخلات حالة أو مخرجاتها أو إجابتها المتوقعة أو تسمياتها. يحمل التسليم 50 عنصرًا على الأكثر؛ ويعدّ more الأحداث الزائدة عنها.
التحقق من تسليم
يحمل كل تسليم ثلاث ترويسات، بصيغة Standard Webhooks:
- webhook-id: معرّف التسليم، وهو نفسه في كل إعادة محاولة.
- webhook-timestamp: وقت إرسال هذه المحاولة، بالثواني منذ 1970.
- webhook-signature: v1, متبوعًا بترميز base64 لـ HMAC-SHA256 على id.timestamp.body، بمفتاح هو بايتات السر (الجزء الذي يلي whsec_، بعد فك ترميز base64).
احسب التوقيع على الجسم الخام كما وصل، وقارنه في زمن ثابت، وارفض أي طابع زمني يبعد أكثر من خمس دقائق عن ساعتك.
import base64, hashlib, hmac, time
def verify(secret: str, headers: dict, body: bytes) -> bool:
key = base64.b64decode(secret.removeprefix("whsec_"))
msg = f"{headers['webhook-id']}.{headers['webhook-timestamp']}.".encode() + body
expected = base64.b64encode(hmac.new(key, msg, hashlib.sha256).digest()).decode()
fresh = abs(time.time() - int(headers["webhook-timestamp"])) <= 300
given = [part.split(",", 1)[-1] for part in headers["webhook-signature"].split()]
return fresh and any(hmac.compare_digest(expected, sig) for sig in given)import { createHmac, timingSafeEqual } from "node:crypto";
export function verify(secret, headers, body) {
const key = Buffer.from(secret.replace(/^whsec_/, ""), "base64");
const id = headers["webhook-id"], ts = headers["webhook-timestamp"];
const expected = createHmac("sha256", key).update(`${id}.${ts}.${body}`).digest("base64");
const fresh = Math.abs(Date.now() / 1000 - Number(ts)) <= 300;
return fresh && headers["webhook-signature"].split(" ").some((part) => {
const sig = Buffer.from(part.split(",").pop());
return sig.length === expected.length && timingSafeEqual(sig, Buffer.from(expected));
});
}مكتبات Standard Webhooks تتحقق من الترويسات نفسها.
التسليم
أجب بأي حالة 2xx خلال عشر ثوانٍ. أي شيء آخر، بما في ذلك إعادة التوجيه، محاولة فاشلة، تُعاد بعد 1 و5 و30 و120 و360 دقيقة ثم يُتخلّى عنها. ولأن الإجابة قد تضيع بعد أن يتصرف خادمك بناءً على التسليم، قد يصل التسليم نفسه مرتين: استخدم webhook-id لإسقاط التكرار. تُطلَق القاعدة مرة واحدة على الأكثر لكل حدث، لذا فالمعرّف الجديد تسليم جديد.
قبل كل محاولة يُحَلّ اسم نقطة النهاية من جديد، ولا يذهب الطلب إلا إلى عنوان عام: تُرفَض عناوين loopback والخاصة وlink-local والمشتركة والمحجوزة وعناوين بيانات السحابة الوصفية، وكذلك صيغ IPv6 التي تحمل أحدها. يُجرى الاتصال بالعنوان الذي جرى فحصه، ويُفحَص الشهادة مقابل الاسم.
Slack
نقطة نهاية Slack هي عنوان URL لـ incoming webhook. تتلقى رسالة قصيرة بالإنجليزية، سطرًا واحدًا لكل حدث، مع رابط إلى كل نتيجة. لا يتحقق Slack من ترويسات التوقيع، التي تُرسَل على أي حال.