Webhooks
سجّل نقطة نهاية عامة، وتحقق من الحمولات الخام الموقّعة، وأزل تكرار الأحداث، وتعامل مع إعادة المحاولات بأمان.
سجّل نقطة نهاية عامة، وتحقق من الحمولات الخام الموقّعة، وأزل تكرار الأحداث، وتعامل مع إعادة المحاولات بأمان.
ترسل Webhooks الخاصة بالمطورين أحداث الحساب إلى الخادم لديك، فلا تحتاج إلى الاستعلام الدوري. التسليم يتم مرة واحدة على الأقل: قد تتكرر الأحداث أو تتأخر أو تصل بترتيب مختلف.
تسجيل نقطة نهاية#
أنشئ مفتاحًا بالصلاحية webhooks:write، ثم سجّل مستقبِلًا عامًا عبر HTTPS مع أصغر مجموعة أحداث تحتاج إليها.
curl --fail-with-body https://makeagent.fast/api/v1/webhook-endpoints \
-X POST \
-H "Authorization: Bearer $MAF_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: webhook-production-v1" \
-d '{
"url": "https://api.example.com/webhooks/make-agent-fast",
"events": ["lead.created", "conversation.started"]
}'يجب أن يستخدم عنوان URL بروتوكول HTTPS، ولا يجوز أن يحتوي على اسم مستخدم أو كلمة مرور أو جزء (fragment) أو منفذ مخصص، ويجب أن يُحلَّ إلى عناوين IP عامة فقط. لا تُتبع عمليات إعادة التوجيه أثناء التسليم.
تكشف الاستجابة 201 عن سر التوقيع whsec_... مرة واحدة فقط. خزّنه في مدير الأسرار الخاص بالمستقبِل قبل إغلاق الاستجابة. عند عرض نقاط النهاية لاحقًا ستحصل على عنوان URL والأحداث والحالة وآخر عملية تسليم وآخر خطأ، لكن دون السر.
أنواع الأحداث#
| الحدث | يُرسَل عندما |
|---|---|
site.published | يصبح الموقع منشورًا |
site.unpublished | يعود الموقع إلى حالة المسودة |
lead.created | يلتقط الـ agent عميلًا محتملًا (Lead) جديدًا |
lead.qualified | يؤهّل الـ agent عميلًا محتملًا، أو يُقبل العميل المحتمل في مسار المبيعات |
booking.requested | يطلب زائر موعدًا أو يحجزه |
handoff.requested | يحوّل الـ agent محادثة إلى شخص (Handoff) |
opportunity.created | يتحول عميل محتمل إلى فرصة في مسار المبيعات |
opportunity.updated | يتغير إسناد الفرصة أو قيمتها أو إجراؤها التالي أو حقول أخرى فيها |
opportunity.stage_changed | تنتقل الفرصة إلى مرحلة أخرى |
opportunity.won | تُغلق الفرصة كصفقة رابحة |
opportunity.lost | تُغلق الفرصة كصفقة خاسرة |
conversation.started | تبدأ سلسلة محادثة جديدة |
conversation.message.created | تُضاف رسالة إلى محادثة |
knowledge.ready | يكتمل تجهيز مصدر معرفة (Source) |
broadcast.sent | تنتهي رسالة جماعية من سير عمل الإرسال |
domain.activated | حدث متقاعد: النطاقات المخصصة غير متاحة، ولا يُرسَل هذا الحدث أبدًا |
تنسيق الطلب#
يرسل Make Agent Fast طلب POST مع Content-Type: application/json وUser-Agent: MakeAgentFast-Webhooks/1.0 والترويسات التالية:
maf-event-id: evt_...
maf-event-type: lead.created
maf-signature: t=1784210566,v1=HEX_HMAC_SHA256يتخذ نص JSON غلافًا ثابتًا. تعتمد الحقول داخل data على الحدث، ويمكن أن تُضاف إليها حقول جديدة.
{
"id": "evt_2c34...",
"type": "lead.created",
"created_at": "2026-07-16T10:02:46.000Z",
"data": {
"site_id": "SITE_ID",
"lead_id": "LEAD_ID"
}
}استخدم id من النص أو maf-event-id مفتاحًا لإزالة التكرار. لا تستخدم وقت التسليم ولا معرّف قاعدة بيانات مولَّدًا.
التحقق من التوقيع في Node.js#
اقرأ البايتات الخام كما وصلت قبل تحليل JSON. القيمة الموقّعة هي TIMESTAMP + "." + RAW_BODY.
import { createHmac, timingSafeEqual } from "node:crypto";
export function verifyWebhook(rawBody: Buffer, header: string, secret: string) {
const values = Object.fromEntries(header.split(",").map((part) => part.split("=", 2)));
const timestamp = values.t;
const supplied = values.v1;
if (!timestamp || !supplied || !/^[a-f0-9]{64}$/.test(supplied)) return false;
const age = Math.abs(Date.now() / 1_000 - Number(timestamp));
if (!Number.isFinite(age) || age > 300) return false;
const expected = createHmac("sha256", secret)
.update(`${timestamp}.`)
.update(rawBody)
.digest("hex");
return timingSafeEqual(Buffer.from(expected, "hex"), Buffer.from(supplied, "hex"));
}ارفض التوقيعات المشوّهة والطوابع الزمنية الأقدم من نافذة إعادة التشغيل (replay) المقبولة لديك؛ وخمس دقائق قيمة افتراضية معقولة. قارن البايتات ذات الطول الثابت بزمن ثابت.
التحقق من التوقيع في Python#
import hashlib
import hmac
import time
def verify_webhook(raw_body: bytes, header: str, secret: str) -> bool:
values = dict(part.split("=", 1) for part in header.split(",") if "=" in part)
timestamp = values.get("t", "")
supplied = values.get("v1", "")
try:
if abs(time.time() - int(timestamp)) > 300:
return False
except ValueError:
return False
signed = timestamp.encode() + b"." + raw_body
expected = hmac.new(secret.encode(), signed, hashlib.sha256).hexdigest()
return hmac.compare_digest(expected, supplied)لا تحلّل JSON ولا تعالجه إلا بعد نجاح التحقق من التوقيع.
الإقرار والمعالجة#
- تحقق من التوقيع والطابع الزمني.
- أدرج معرّف الحدث في جدول بقيد تفرّد (unique constraint).
- إذا كان موجودًا مسبقًا، فأعد
204دون تكرار الأثر الجانبي. - ثبّت الحدث (commit) أو أضف مهمتك الداخلية إلى قائمة الانتظار.
- أعد استجابة
2xxبسرعة.
ينتهي مهلة المرسِل في Make Agent Fast بعد 10 ثوانٍ. ويعدّ استجابات 2xx فقط ناجحة، ولا يتبع عمليات إعادة التوجيه.
سلوك إعادة المحاولة#
توضع عمليات التسليم الفاشلة في قائمة انتظار بحد أقصى ثماني محاولات. يبدأ التأخير التراجعي (backoff) عند نحو ثانيتين، ويتضاعف مع تشويش عشوائي (jitter)، ويبلغ حده الأقصى ساعة واحدة؛ وقد يتأخر وقت التسليم الفعلي عندما تكون العمليات المنفِّذة مشغولة. ولأن إعادة المحاولات قد تتجاوز عمر الطلب الذي أطلقها وقد تصل بترتيب مختلف، فلا تعتمد أبدًا على وصول حدث ما مباشرة قبل حدث آخر.
أعد استجابة غير 2xx فقط عندما تريد أن يعيد Make Agent Fast المحاولة. وفي حالة إصدار حدث غير مدعوم نهائيًا أو وجهة محذوفة، اقبل الحدث وسجّله أو احذف نقطة النهاية، بدلًا من إنتاج إخفاقات مؤقتة لا تنتهي.
تدوير سر التوقيع#
لا يمكن كشف أسرار التوقيع ولا تعديلها. لتدويرها بأمان:
- أنشئ نقطة نهاية ثانية تشير إلى مسار مستقبِل مؤقت أو مُرقَّم بإصدار.
- خزّن السر الجديد الذي كُشف لك.
- اقبل الأحداث من نقطتي النهاية معًا مع إزالة التكرار.
- تأكد أن نقطة النهاية الجديدة تتلقى عمليات تسليم صالحة.
- احذف نقطة النهاية القديمة عبر
DELETE /webhook-endpoints/{endpoint_id}.
إذا بقي عنوان URL الخاص بالمستقبِل كما هو، فاجعل التطبيق المستقبِل يقبل السرّين خلال فترة التداخل، واستخدم معرّفات الأحداث لمنع تكرار الآثار الجانبية.
استكشاف الأخطاء وإصلاحها#
| العَرَض | ما يجب فحصه |
|---|---|
| لا توجد عمليات تسليم | حالة نقطة النهاية، والأحداث المحددة، وDNS العام، وشهادة HTTPS، وهل وقع الحدث فعلًا |
| عدم تطابق التوقيع | الوصول إلى النص الخام، والتسلسل الدقيق timestamp.body، وسر نقطة النهاية الصحيح، وأي تحويلات يجريها الوكيل (proxy) على النص |
| عمليات تسليم متكررة | رمز الاستجابة، ومهلة 10 ثوانٍ، والاستثناءات الداخلية، وتفرّد معرّف الحدث |
| خطأ عنوان خاص | يجب أن تُحلّ سجلات DNS إلى عناوين عامة فقط؛ يُرفض localhost والنطاقات الداخلية |
| فشل إعادة التوجيه | سجّل عنوان HTTPS النهائي مباشرة؛ لا تُتبع عمليات إعادة التوجيه |
لا تضمّن سر التوقيع أو مفتاح bearer أو الحمولة الكاملة للزائر في أي رسالة إلى الدعم. أرفق معرّف نقطة النهاية ومعرّف الحدث ووقت الفشل وسجلات المستقبِل بعد تنقيتها.