انتقل إلى المحتوى

الويب هوك

عرض بصيغة Markdown

يخبر الويب هوك أنظمتك بما يحدث في محادثاتك لحظة حدوثه: تحويل يجب أن يستلمه فريقك، أو رد يُوصَل عبر قناة أخرى، أو تذكرة جديدة، أو تنبيه استخدام. يوقّع K-Agent كل إرسال وفق Standard Webhooks، فتستطيع إثبات أنه صادر منا.

نافذة الطرفية
curl https://api.k-agent.kerneltics.com/v1/webhook_endpoints \
-H "Authorization: Bearer $KAGENT_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"url": "https://hooks.example.com/kagent",
"description": "نظام الدعم",
"events": ["handoff.requested", "handoff.resolved", "message.created", "ticket.created"]
}'

تتضمن الاستجابة سر التوقيع secret ‏(whsec_…) مرة واحدة — احفظه كما تحفظ كلمة المرور. وغيّره عبر POST /v1/webhook_endpoints/{whep}/rotate_secret، الذي يعرض السر الجديد مرة واحدة أيضًا.

  • events تسرد أنواع الأحداث المطلوبة، أو ["*"] (الافتراضي) لكل الأحداث. والنوع غير الموجود في القائمة أدناه يعيد 422 unknown_event_type.
  • يجب أن يكون الرابط https:// عامًا على المنفذ 443 أو 8443؛ وتُرفض العناوين الخاصة والداخلية، ولا تُتبع التحويلات (redirects).
  • للمشروع 10 نقاط استقبال كحد أقصى.
الحدث يُرسل حين
message.created تُحفظ رسالة من العميل أو المساعد أو رسالة human_agent عامة. لا تُضمَّن الملاحظات الداخلية أبدًا.
note.created يضيف فريقك ملاحظة داخلية.
run.completed وrun.failed وrun.requires_action ينتهي تشغيل، أو يفشل، أو ينتظر نتائج أدوات جهة العميل.
session.created وsession.updated وsession.closed تبدأ جلسة، أو يتغير وضعها أو حالتها، أو تُغلق.
handoff.requested وhandoff.assigned وhandoff.unclaimed وhandoff.resolved وhandoff.expired دورة حياة التحويل لموظف.
ticket.created فتح الوكيل (أو الكود الخاص بك) تذكرة.
conversation.flagged وسم الوكيل محادثة للمراجعة.
usage.threshold_reached بلغ الاستخدام 75% أو 100% من الخطة ({percent}).
knowledge_source.sync_failed فشل تحديث مصدر معرفة من واجهة برمجية.
webhook_endpoint.disabled عُطّلت نقطة استقبال بعد فشلها 5 أيام.

أحداث لوحة التجربة في لوحة التحكم لا تُرسل أبدًا.

كل إرسال طلب POST بجسم JSON:

{
"type": "handoff.requested",
"id": "evt_01k6rz8e0h2k4n6q8s0v2x4z6b",
"created_at": 1791272100,
"data": {
"handoff": {
"id": "ho_01k6rz6c8e0g2j4m6p8r0t2v4x",
"object": "handoff",
"session_id": "sess_01k6rz4p7h2c9m5x8w3t6v1qbg",
"run_id": "run_01k6rz5a9d3f6g2h8j4k7m1n5p",
"reason_type": "liability",
"summary": "العميل يذكر أن العطر سبّب حساسية في الجلد",
"status": "open",
"requested_by": "agent",
"priority": "high",
"expires_at": 1791358500
}
}
}

وثلاث ترويسات:

الترويسة القيمة
webhook-id معرّف الحدث (evt_…). يبقى ثابتًا في كل إعادة محاولة — استخدمه لمنع التكرار.
webhook-timestamp ثوانٍ بتوقيت يونكس لحظة توقيع هذه المحاولة.
webhook-signature توقيع واحد أو أكثر تفصل بينها مسافات، كلٌّ بالصيغة v1,<base64>.

التوقيع HMAC-SHA256 للنص {webhook-id}.{webhook-timestamp}.{raw body}، ومفتاحه الجزء الواقع بعد whsec_ في سرك بعد فك ترميز base64. تحقّق من البايتات الخام للجسم — قبل أي تحليل JSON — وقارن بزمن ثابت، وارفض الطوابع الزمنية التي تبعد أكثر من خمس دقائق عن ساعتك. هذه التطبيقات مُختبَرة على متجه الاختبار الخاص بـ Standard Webhooks:

// Node.js + Express
import crypto from 'node:crypto';
import express from 'express';
const TOLERANCE_SECONDS = 5 * 60;
export function verifyWebhook(rawBody, headers, secret) {
const id = headers['webhook-id'];
const timestamp = headers['webhook-timestamp'];
const signatures = headers['webhook-signature'];
if (!id || !timestamp || !signatures) throw new Error('Missing webhook headers');
const age = Math.abs(Date.now() / 1000 - Number(timestamp));
if (!(age <= TOLERANCE_SECONDS)) throw new Error('Timestamp outside the tolerance window');
const key = Buffer.from(secret.replace(/^whsec_/, ''), 'base64');
const expected = crypto
.createHmac('sha256', key)
.update(`${id}.${timestamp}.`)
.update(rawBody)
.digest();
const valid = signatures.split(' ').some((entry) => {
const [version, signature] = entry.split(',');
if (version !== 'v1' || !signature) return false;
const given = Buffer.from(signature, 'base64');
return given.length === expected.length && crypto.timingSafeEqual(given, expected);
});
if (!valid) throw new Error('Invalid signature');
return JSON.parse(rawBody.toString('utf8'));
}
const app = express();
// express.raw keeps the exact bytes that were signed.
app.post('/kagent', express.raw({ type: 'application/json' }), (req, res) => {
let event;
try {
event = verifyWebhook(req.body, req.headers, process.env.KAGENT_WEBHOOK_SECRET);
} catch {
return res.sendStatus(400);
}
res.sendStatus(204); // acknowledge first, then process
queue.push(event); // your own job queue; deduplicate on event.id
});

وتوجد مكتبات Standard Webhooks الرسمية للغات كثيرة أيضًا (standardwebhooks على npm وPyPI)، وهي تقبل سر whsec_ نفسه.

  • أعد أي حالة 2xx خلال 15 ثانية. وأي حالة أخرى، أو انتهاء المهلة، يُعدّ فشلًا.
  • أكّد الاستلام أولًا، ثم أنجز العمل في مهمة خلفية.
  • امنع التكرار بالاعتماد على webhook-id (معرّف الحدث id): قد يصل الإرسال نفسه أكثر من مرة.
  • لا تعتمد على الترتيب. استخدم created_at، أو اجلب الحالة الحالية من الواجهة البرمجية (GET /v1/handoffs/{id} وGET /v1/sessions/{id}).

يُعاد إرسال الطلبات الفاشلة بعد نحو 5 ثوانٍ، ثم 5 دقائق، ثم 30 دقيقة، ثم ساعتين، ثم 5 ساعات، ثم 10 ساعات، ثم 10 ساعات، ثم يُعلَّم الإرسال فاشلًا. ونقطة الاستقبال التي يستمر فشلها 5 أيام تُعطَّل مع ذكر السبب، ويُرسل الحدث webhook_endpoint.disabled إلى نقاطك الأخرى.

المهمة الطلب
إرسال حدث تجريبي (ping، أو عينة من النوع type الذي تمرره) POST /v1/webhook_endpoints/{whep}/test
عرض الإرسالات الأخيرة واستجاباتها GET /v1/webhook_endpoints/{whep}/deliveries
إعادة إرسال واحد الآن POST /v1/webhook_deliveries/{whd}/retry
تغيير الرابط أو الأحداث أو الحالة PATCH /v1/webhook_endpoints/{whep}
تصفح سجل الأحداث GET /v1/events وGET /v1/events/{evt}

تُحفظ الأحداث 30 يومًا.