تسلّم الـ webhooks أحداث التعرّف إلى خادمك في الوقت الفعلي. في كل مرة يجري فيها
التعرّف على زائر، يرسل TRACIO طلب POST عبر HTTP إلى عنوان الـ webhook الذي ضبطته.
وجسم الطلب هو نفسه حمولة الحدث.
وهي أيضًا القناة الوحيدة التي تسلّم الأحكام المتأخرة: تلك التي أثبت فيها سلوك الزائر أنه آلي بعد أن كانت الصفحة قد حُمّلت بالفعل.
اضبطها من لوحة التحكم في Settings → Webhooks. تتطلب الـ webhooks خطة Pro أو أعلى.
| الحدث | متى | الخطة |
|---|---|---|
identification | في كل زيارة — مراحل primary وlate وcorrection | الكل |
account_takeover | لم يعد السلوك تحت الحساب مطابقًا لملف صاحبه | Business+ |
account_assessment | حالة تقييم الاستيلاء على الحساب لحساب ما: حُكم عليه وهو سليم، أو حُكم عليه وهو منحرف، أو لم يُحكم عليه | Business+ |
attack_detected | ارتفاع مفاجئ في عدد البوتات على موقعك | Business+ |
reputation_changed | انتقلت سمعة شخص مرتبط إلى نطاق آخر | Business+ |
transaction_state_changed | تغيّرت نتيجة معاملة أرسلتها إلى POST /v2/transactions | Business+ |
login_risk | انتهى تقييم تسجيل دخول عبر POST /v2/logins/assess بـ challenge أو review | الكل |
trusted_device_enrolled | صار جهاز موثوقًا لحساب ما، أو جُدّدت الثقة به | الكل |
trusted_device_revoked | أُلغيت الثقة بجهاز — عبر API أو من لوحة التحكم | الكل |
affiliate_threshold_crossed | ساءت التوصية بشأن شريك تسويق بالعمولة (none → review، none → hold، review → hold) | الكل |
تستخدم أسماء الأحداث الشرطة السفلية لا النقاط أبدًا — فلا وجود لـ visitor.created ولا
لـ session.created.
يشترك الـ webhook في أنواع محددة؛ أما القيمة المنفصلة * فتعني «كل نوع، بما في ذلك
الأنواع التي تُضاف لاحقًا». وتُستثنى خمسة أنواع: فـ * لا يشمل account_assessment
وlogin_risk وtrusted_device_enrolled وtrusted_device_revoked
وaffiliate_threshold_crossed — سمِّ كلًّا منها صراحةً. فـ account_assessment يبلّغ
عن التغطية لا عن نتيجة، ولذلك يصل تقريبًا مرة لكل زيارة تحمل linked_id — وهو حجم من
رتبة مختلفة عن بقية الأحداث. وأنواع مخاطر تسجيل الدخول والتسويق بالعمولة لا معنى لها إلا
لتكامل يستدعي تلك الواجهات. والـ wildcard الذي ضبطته قبل وجود هذه الأحداث ليس موافقة على
تلقّيها.
والنوع غير المعروف يُرفض بالرمز 400 عند إنشاء الاشتراك أو تعديله، فلا يتركك خطأ مطبعي
مع webhook لا يُطلق أبدًا في صمت.
identificationتنتج الزيارة الواحدة ما يصل إلى ثلاث عمليات تسليم تتشارك القيمة نفسها لـ request_id:
primary — الحكم الأولي، عند تحميل الصفحة.late — إثراء بعد نحو تسع ثوانٍ، متى وصلت الفحوصات البطيئة.correction — تصحيح مبني على السلوك (المؤشر، لوحة المفاتيح، التمرير).اربطها ببعضها عبر request_id، وميّز بينها عبر phase. المرحلة الأحدث هي المقدَّمة:
إذا قالت primary إنه human وقالت correction إنه bot، فالإجابة الصحيحة هي
الثانية.
لا تعتمد على ترتيب الوصول. فكل مرحلة تُسلَّم بشكل مستقل ووفق جدول إعادة محاولات
خاص بها: إذا دخلت primary في إعادة المحاولة بينما نجحت late من المحاولة الأولى،
فستصلك بترتيب معكوس. حدّد الأولوية من حقل phase، لا من وقت الاستلام.
هذه الثلاث هي المراحل الوحيدة لحدث identification. وتصلك قيمة أخرى واحدة: إذ يحمل
account_takeover وaccount_assessment القيمة phase: "beacon"، لأن الاثنين يُرفعان
من الـ beacon السلوكي للجلسة.
انتبه إلى التعارض الذي ينشأ عن ذلك، لأنه يمسّ عدم التكرار (idempotency). فعملية تسليم
identification في الإنتاج يكون event_id فيها بالضبط <request_id>:<phase>، لكن
عمليات التسليم الأخرى تكسر هذه الصيغة. فـ account_takeover يكون <request_id>:ato —
واللاحقة هنا هي الحرفية ato، لا قيمة حقل phase — وaccount_assessment يكون
<request_id>:ato-state-<state>. وعملية التسليم التجريبية المرسلة من لوحة التحكم تكون
<request_id>:test، بينما يظل phase في جسمها يقول primary — أما جسم مخطط 1 فلا
يحتوي على حقل phase إطلاقًا، فلا تظهر تلك اللاحقة إلا في الترويسة. استخدم event_id
مفتاحًا لعدم التكرار مباشرةً، ولا تعِد تركيبه أبدًا من request_id وphase. طابق على
القيم التي تعالجها وتجاهل ما عداها بدل أن ترفض عملية التسليم.
بعض الأحداث لا زيارة لها: attack_detected (على مستوى مساحة العمل)، وreputation_changed
(على مستوى الشخص)، وtransaction_state_changed (على مستوى المعاملة)، وlogin_risk
وtrusted_device_enrolled وtrusted_device_revoked (على مستوى الحساب)،
وaffiliate_threshold_crossed (على مستوى شريك التسويق بالعمولة). ليس لها request_id
ولا visitor_id ولا شيء من كتل browser أو geo أو bot أو decision — هذه المفاتيح
غائبة ببساطة، وكذلك ترويسة X-Tracio-Request-Id. أما account_takeover
وaccount_assessment فينتجان عن زيارة بعينها ويحملان جسم التعرّف الكامل الخاص بخطتك.
وإذا كنت تحلّل كل الأحداث في معالج واحد، فتحقّق من event قبل أن تلمس حقول الزيارة.
تستخدم الـ webhooks الجديدة الإصدار 3 من الحمولة. أما الـ webhooks القائمة على الإصدار 1 أو 2 فتحتفظ بالإصدار الذي اختارته؛ والمخططان الأقدم مجمَّدان وسيُزالان في 2027-04-12. وللانتقال، اختر الإصدار 3 على بطاقة الـ webhook في لوحة التحكم.
| الإصدار | الحالة |
|---|---|
1 | مجمَّد؛ يُزال في 2027-04-12 |
2 | مجمَّد؛ يُزال في 2027-04-12 |
3 | الافتراضي للـ webhooks الجديدة؛ مفاتيح بصيغة snake_case |
يحمل الإصدار 3 الحقول والقيم نفسها التي يحملها الإصدار 2، مع مفاتيح بصيغة snake_case
وversion: 3 في المستوى الأعلى. ولم تتغيّر خوارزمية التوقيع ولا الترويسات
(X-Tracio-Payload-Version: 3). أما المفاتيح داخل قاموس fields الخاص بك فهي بياناتك
ولا تُعاد تسميتها.
| v2 | v3 |
|---|---|
eventId | event_id |
requestId | request_id |
visitorId | visitor_id |
linkedId | linked_id |
userAgent | user_agent |
network.connectionType | network.connection_type |
network.proxyDetected | network.proxy_detected |
network.realIp | network.real_ip |
bot.antidetectScore | bot.antidetect_score |
identification.matchType | identification.match_type |
identification.matchConfidence | identification.match_confidence |
identification.incognitoVisits | identification.incognito_visits |
identification.driftScore | identification.drift_score |
decision.riskScore | decision.risk_score |
decision.suspectScore | decision.suspect_score |
deviceInfo | device_info |
deviceInfo.deviceId | device_info.device_id |
deviceInfo.crossBrowser | device_info.cross_browser |
deviceInfo.linkedBrowsers | device_info.linked_browsers |
screen.colorDepth | screen.color_depth |
screen.pixelRatio | screen.pixel_ratio |
clientHints | client_hints |
clientHints.platformVersion | client_hints.platform_version |
clientHints.deviceName | client_hints.device_name |
osEnvironment | os_environment |
environment.virtualMachine | environment.virtual_machine |
spoofing.spoofedAxes | spoofing.spoofed_axes |
spoofing.anonymousBrowser | spoofing.anonymous_browser |
session.durationSeconds | session.duration_seconds |
velocity.uniqueIps | velocity.unique_ips |
velocity.uniqueLocations | velocity.unique_locations |
accountAlert | account_alert |
accountAlert.accountId | account_alert.account_id |
accountAlert.driftScore | account_alert.drift_score |
accountAssessment | account_assessment |
accountAssessment.accountId | account_assessment.account_id |
accountAssessment.profile.windowsEffective | account_assessment.profile.windows_effective |
accountAssessment.profile.oldestWindowAgeSeconds | account_assessment.profile.oldest_window_age_seconds |
attack.windowMinutes | attack.window_minutes |
attack.recentBots | attack.recent_bots |
attack.recentTotal | attack.recent_total |
personId | person_id |
reputation.overallRisk | reputation.overall_risk |
linkedAccountsCount | linked_accounts_count |
linkedVisitorsCount | linked_visitors_count |
reputationChange | reputation_change |
reputationChange.personId | reputation_change.person_id |
reputationChange.previousBand | reputation_change.previous_band |
reputationChange.visitorIds | reputation_change.visitor_ids |
reputationChange.linkedAccountsCount | reputation_change.linked_accounts_count |
{ "version": 3, "event": "identification", "event_id": "9c1f6a2e-3b7d-4c58-a1e2-6f0d8b4a7c31:primary", // "<request_id>:<phase>" — the idempotency key "request_id": "9c1f6a2e-3b7d-4c58-a1e2-6f0d8b4a7c31", // visit identifier, shared by all phases "phase": "primary", "visitor_id": "X7fh2Hg9LkMn3pQr5tBvQw3xZa9mK2pL4nR8dT6y", "linked_id": "user-42", // your ?lid=, if you passed one "tag": "checkout", "fields": { "order_ref": "A-1029", "plan": "pro" }, // custom fields that arrived with the visit "timestamp": "2026-07-30T12:00:00Z", "url": "https://shop.example.com/checkout", "ip": "203.0.113.44", "user_agent": "Mozilla/5.0 …", "browser": { "name": "Chrome", "version": "138" }, "os": { "name": "macOS", "version": "15.5" }, "device": "desktop", "gpu": "Intel Iris Plus Graphics 655", // طراز مُهايئ الفيديو، مُطبَّع؛ غائب عندما يكون غير معروف "geo": { "country": "DE", "city": "Berlin", "lat": 52.52, "lon": 13.405, "timezone": "Europe/Berlin" }, "network": { "vpn": false, "proxy": true, "tor": false, "datacenter": true, "connection_type": "DCH", "proxy_detected": true // حركة HTTP والمسار الشبكي الخام يخرجان عبر شبكتين مختلفتين }, "bot": { "result": "human", "score": 3.2 }, // human | bot | uncertain "identification": { "confidence": 0.97, "incognito": false }, "decision": { "action": "real", "risk_score": 12.2 }, // real | fake | suspicious "extensions": ["metamask", "privacy_badger"] // detected browser extensions; absent when none}تُحذف القيم الصفرية والفارغة. فالحقول النصية والرقمية ذات القيمة الصفرية (مثل
bot.type لإنسان، أو bot.antidetect_score: 0) غائبة عن JSON — لا تجعلها مطلوبة في
مخططاتك، واقرأ الكتل المتداخلة بحذر.
bot.score وdecision.risk_score أرقام عشرية على مقياس 0..100 بخانة واحدة بعد
الفاصلة العشرية — وهي بالضبط الأرقام التي تبلّغ عنها لوحة التحكم وData API للزيارة
نفسها. (وفي مخطط v1 المجمَّد تستخدم وحدات مختلفة: كسر على 0..1 و0..255 على الترتيب.)
bot.type إما اسم بوت معروف، وإما وكيل ذكاء اصطناعي موثَّق (ai_agent:<vendor>)، وإما
تسمية عائلة. راجع أنواع البوتات للاطلاع على المفردات —
ولا تُكشف أسماء الفحوصات الداخلية في أي خطة. ويعني ai_agent:<vendor> أن الطلب أثبت
أنه جاء من البنية التحتية لذلك المزوّد — توقيع Web Bot Auth صالح بمفتاح ينشره المزوّد، أو
عنوان ضمن نطاقات IP التي ينشرها المزوّد — لا مجرد ادّعاء ذلك في user agent. والطلب الذي
يدّعي أنه وكيل ذكاء اصطناعي وتناقضه كل الأدلة التي ينشرها المزوّد يُبلَّغ عنه بوصفه
impersonation.
| الحقل | النوع | الوصف |
|---|---|---|
version | number | إصدار مخطط الحمولة (3) |
event | string | نوع الحدث |
event_id | string | معرّف التسليم — مفتاح idempotency؛ فريد لكل حدث ومشترك بين كل الـ webhooks لديك التي تتلقّاه، وثابت عبر إعادات المحاولة |
request_id | string | معرّف الزيارة (UUID)، مشترك بين كل مراحلها |
phase | string | primary وlate وcorrection؛ ويحمل account_takeover وaccount_assessment القيمة beacon |
visitor_id | string | معرّف زائر مستقر |
linked_id | string | معرّف مرتبط يوفّره العميل |
tag | string | وسم مخصص يوفّره العميل |
fields | object | الحقول المخصّصة المُرفقة بالزيارة لحظة الجمع. فقط ما وصل مع الزيارة — والحقل المُرفق لاحقًا عبر Data API لا يظهر أبدًا في webhook، في أي مرحلة أو إعادة محاولة. المفاتيح والقيم سلاسل نصية. غائب عندما لا تحمل الزيارة حقولًا |
timestamp | string | وقت الحدث (RFC 3339) |
url | string | عنوان الصفحة التي التُقط فيها الحدث |
ip | string | عنوان IP للعميل |
user_agent | string | سلسلة user-agent الخام للعميل |
browser.name / .version | string | المتصفح المكتشَف |
os.name / .version | string | نظام التشغيل المكتشَف |
device | string | فئة الجهاز (مثل desktop وmobile) |
gpu | string | طراز مُهايئ الفيديو كما يبلّغ عنه المتصفح (WebGL)، مُطبَّع إلى اسم مقروء (Intel Iris Xe Graphics، Apple M1 Pro)؛ وSoftware renderer لمُصيِّرات البرمجيات؛ غائب عندما يكون غير معروف |
geo | object | الموقع الجغرافي حسب IP: country، city، lat، lon، timezone |
network | object | vpn وproxy وtor وdatacenter (قيم منطقية) وconnection_type |
network.proxy_detected | boolean | حركة HTTP الخاصة بالزيارة ومساراتها الشبكية الخام تخرج عبر شبكات مختلفة — أي وجود بروكسي أو VPN أمام المتصفح. وعنوانان لدى المزوّد نفسه (NAT المشغّل، أو مخرج ثانٍ لنفس الـVPN) لا يُحتسبان |
bot.result | string | human أو bot أو uncertain |
bot.type | string | اسم البوت أو وكيل ذكاء اصطناعي موثَّق أو تسمية العائلة عند اكتشاف بوت |
bot.score | number | درجة البوت (0–100) |
identification.confidence | number | ثقة التعرّف (0.0–1.0) |
identification.incognito | boolean | سياق التصفح الخاص/المتخفي |
decision.action | string | real أو fake أو suspicious |
decision.risk_score | number | درجة المخاطر الإجمالية (0–100) |
extensions | array | إضافات المتصفح المكتشفة في هذه الزيارة، بوصفها معرّفات آلية ثابتة (slugs) (الأسماء والفئات وروابط المتجر موجودة في لوحة التحكم وData API). غائب عندما لا يُكتشف شيء |
Pro وما فوقها — كيف يتصرف الزائر عبر الزمن:
{ "identification": { "match_type": "exact", // exact | fuzzy | new — how the visitor was recognized "match_confidence": 0.93, "visits": 42, "incognito_visits": 3 }, // Present when visitor counters are available at event time (usually primary). // A missing block means "no data", not "zeros". "velocity": { "events5m": 7, "unique_ips": 2, "unique_locations": 1 }, "bot": { "antidetect_score": 0 }, // antidetect indicators, 0..100 "session": { "duration_seconds": 95 } // the correction phase and the account_takeover alert}Business وما فوقها — لماذا جاء الحُكم على هذا النحو:
{ "reasons": [ // at most 8, sorted by importance { "code": "headless_browser", "severity": "high" }, { "code": "privacy_hardening", "severity": "low" } ], // Behavioral biometrics — present only when behavioral scoring ran for the // visit. A missing block means "no data", never "nothing suspicious". "behavior": { "score": 87, "verdict": "human", "confidence": 0.92 }, // The smoothed behavior score the account-takeover verdict was made on. It is // measured by beacons that arrive after the identification event, so it is // empty in identification deliveries — see the account_takeover event. "identification": { "drift_score": 0.41 }, "device_info": { "device_id": "…", // the physical device across browsers on it "cross_browser": true, "confidence": 0.88, "linked_browsers": 3 }, "network": { "isp": "Deutsche Telekom", "asn": 3320, // العنوان العام المرصود على المسار الشبكي الخام، أي العنوان خلف البروكسي // أو الـVPN؛ غائب عندما لا يُرصد عنوان كهذا "real_ip": { "address": "203.0.113.7", "country": "NL", "isp": "KPN" } }, "geo": { "isp": "Deutsche Telekom" }, // بيئة سطح المكتب المقيسة فعليًا على جهاز Linux ("Mint 22+"، "Ubuntu"، // "GNOME"، "KDE")؛ فالـUser-Agent لا يستطيع التعبير عن التوزيعة. غائبة // عندما لا تُحدَّد — في معظم زيارات Linux، وفي كل زيارة غير Linux. "os_environment": "Mint 22+", // ما ادّعته الزيارة عن نفسها مقابل ما قاسته فحوص مستقلة. موجود فقط عندما // يُكتشف انتحال فعلي؛ وحقل `real` الفارغ يعني "بقي الفحص صامتًا"، لا "مؤكَّد" // أبدًا. وعلى محور `gpu` يحمل `claimed.gpu` المُهايئ المُدَّعى باسم الطراز // المقروء نفسه الوارد في حقل `gpu` الأعلى مستوى. "spoofing": { "detected": true, "claimed": { "os": "Windows 10", "browser": "Chrome 139.0" }, "real": { "os": "macOS" }, "spoofed_axes": ["os", "screen"], // os | gpu | screen | network | browser "anonymous_browser": { "detected": true, "names": ["Linken Sphere"] } }, // حقائق الجهاز — ما يبلّغه متصفح الزائر عن الآلة، بعد تنقيته لدينا. screen: // الدقة وعمق الألوان وdevice pixel ratio. locale: اللغات المفضّلة للمتصفح // نفسه ومنطقته الزمنية — خلافًا لـgeo.timezone المشتقة من عنوان IP؛ // والتباين بينهما علامة شائعة على موقع منتحَل. client_hints: تلميحات العميل // في User-Agent — معمارية المعالج وعرضه بالبتّات، وطراز الجهاز (على // Android: رمز الطراز في `model`، مثل "SM-A556B"، واسمه التسويقي من قائمة // أجهزة Google Play في `device_name`، مثل "Samsung Galaxy A55 5G")، وإصدار // المنصّة الدقيق؛ ولا تبلّغ عنها إلا المتصفحات المبنية على Chromium. // وكل كتلة تغيب عندما لا تحمل الزيارة بيانات كهذه، فتعامل مع كلٍّ منها على // أنها اختيارية. "screen": { "width": 2560, "height": 1600, "color_depth": 30, "pixel_ratio": 2 }, "locale": { "languages": ["en-US", "de"], "timezone": "Europe/Berlin" }, "client_hints": { "architecture": "arm", "bitness": "64", "platform_version": "15.5.0" }, // موجود فقط عندما يعرّف مُهايئ الفيديو نفسه بأنه افتراضي؛ وhypervisor قاموس // مغلق (vmware، virtualbox، parallels، qemu، hyperv، bochs، intel-gvt، // vgpu). وغياب الكتلة يعني أنه لا دليل من هذا القبيل. "environment": { "virtual_machine": true, "hypervisor": "vmware" }, "decision": { "suspect_score": 4.1 }, "guidance": { "version": 3, "overall": "review" } // see below}راجع كشف البوتات للاطلاع على مفردات رموز
الأسباب وعلى معنى severity.
حقول تعدّد الحسابات (Business وما فوقها). متى فُعِّل وضع الشخص لمساحة عملك، يحمل
الجسم أيضًا person_id، وكتلة reputation (score، categories، overall_risk)،
وlinked_accounts_count وlinked_visitors_count — أي هل الزائر مرتبط بزوّار وحسابات
أخرى. وتعمل طبقة الشخص حاليًا في وضع المراقبة، وإلى أن تُفعَّل فعليًا تغيب هذه الحقول عن
الجسم. لا تجعلها مطلوبة في مخططاتك.
يحمل guidance توصيات جاهزة عن «ما العمل» لكل نقطة تكامل، حتى لا تضطر إلى اشتقاق
سياسة من الدرجات الخام. وهو حاضر في كل حدث v3 لخطط Business+ — والقيمة allow تُذكر
صراحةً:
{ "guidance": { "version": 3, "overall": "review", // the strictest advice across the scenarios "payment": "review", // whether to accept the payment "registration": "challenge", // whether to create the account "login": "challenge", // whether to let them into the account "affiliate": "review", // whether to credit the conversion to the partner "basis": ["risk", "network"] // the axes that determined the advice }}يبدأ كل سيناريو من allow ولا يتحرك على السلّم إلا صعودًا:
allow ← challenge ← review ← deny. وداخل السيناريو الواحد يفوز أشدّ محور جرى
تفعيله، بينما overall هو الأشدّ عبر السيناريوهات الأربعة كلها.
| التوصية | الدفع | التسجيل | تسجيل الدخول | التسويق بالعمولة |
|---|---|---|---|---|
allow | نفّذه | أنشئه | اسمح بالدخول | احتسب التحويل |
challenge | 3-D Secure / تأكيد | كابتشا أو تأكيد بالبريد أو الهاتف | 2FA إضافية، إعادة مصادقة | ضَعه في خانة المشكوك فيه حتى يظهر نشاط |
review | نفّذه، لكن أدرجه في قائمة المراجعة | أنشئه مع قيود | اسمح بالدخول، وأطلق تنبيهًا | احجز الدفعة حتى تتم المراجعة |
deny | لا تنفّذ المعاملة | ارفض إنشاء الحساب | لا تسمح بالدخول | لا تحتسب التحويل |
يسرد basis المحاور التي حدّدت التوصية — والمحور الذي كان اقتراحه أخفّ من المعتمَد
لا يدخل القائمة. اقرأه حسب الرمز بدل افتراض مجموعة ثابتة:
المحور (basis) | ما يعنيه |
|---|---|
list | طابق الزائر إحدى قوائم مساحة عملك في وضع enforce. وقائمة allow تتغلّب على كل المحاور: يصبح كل سيناريو allow ويكون basis بالضبط ["list"] |
rule | طابقت الزيارة إحدى قواعد مساحة عملك في وضع enforce؛ والقاعدة لا تحرّك إلا توصية سيناريوها (وقاعدة allow تليّن ذلك السيناريو وحده)، ويجري تخطّيها إذا كانت قائمة قد تصرّفت بالفعل |
bot | خلص كاشف البوتات إلى أن هذه أتمتة — يرفض كل السيناريوهات |
risk | الحكم ودرجة المخاطر الإجمالية للزيارة |
behavior | تقول القياسات الحيوية السلوكية إنه بوت بثقة كافية — تضيف احتكاكًا لا رفضًا |
account_takeover | لم يعد السلوك تحت هذا الحساب مطابقًا لملف صاحبه — تسجيل الدخول إلى challenge، والدفع إلى review |
network | Tor، أو VPN يخرج من مركز بيانات |
affiliate_pattern | شكل الإحالات المولَّدة ذاتيًا — يُحتجز تحويل التسويق بالعمولة بوصفه challenge |
fraud_reputation, multiaccounting | سمعة الشخص الذي يقف خلف الزائر وتجميع حساباته؛ يظهران حالما تُفعَّل طبقة الشخص |
version هو إصدار مجموعة القواعد — ويُرفع كلما تحسّن المنطق، لذا احتفظ به في سجلاتك.
أدخل الإصدار 2 محور list، وهو أول محور يمكنه تليين التوصية؛ ونقل الإصدار 3 عتبات تسجيل
الدخول الخاصة بـ account_takeover إلى مقياس درجة حكم الاستيلاء على الحساب، فقد تحصل
الزيارة نفسها على deny حيث أعطى الإصدار 2 challenge، والعكس بالعكس.
وguidance تراكمي: تصل السيناريوهات الجديدة كمفاتيح جديدة دون كسر العقد. المرحلة الأحدث
تفوز، باستثناء التوصية الجزئية: فالتسليم المحسوب على مجموعة مدخلات ناقصة يُوسَم بـ
"partial": true، والتوصية الجزئية لا تُلغي توصية كاملة وردت قبلها لنفس
request_id. وفي التسليم العادي لا يوجد حقل partial إطلاقًا.
لا تُوثَّق العتبات الدقيقة عن قصد. فالتوصية التي يمكن ردّها بالهندسة العكسية إلى درجة
تكفّ عن كونها دفاعًا. وثمة استثناء مقصود واحد بالضبط: account_assessment.verdict.threshold،
حدّ القرار لحكم الاستيلاء على الحساب الموصوف أدناه.
account_takeoverلخطتَي Business وEnterprise فقط؛ وفي الخطط الأدنى لا تُنشأ عملية تسليم، حتى لو كان
webhook مشتركًا في هذا الحدث. الجسم هو مغلّف التعرّف الكامل الخاص بخطتك بالإضافة إلى كتلة
account_alert وكتلة account_assessment الموصوفة في القسم التالي، ويُسلَّم مرة واحدة
على الأكثر لكل زيارة:
{ "version": 3, "event": "account_takeover", "event_id": "9c1f6a2e-3b7d-4c58-a1e2-6f0d8b4a7c31:ato", "request_id": "9c1f6a2e-3b7d-4c58-a1e2-6f0d8b4a7c31", "phase": "beacon", // التنبيه يُرفع من beacon؛ وحده event_id يقول "ato" "account_alert": { "type": "behavior-drift/2", // kind of alert and the version of its construction "account_id": "user-42", // your linked_id for the account "drift_score": 0.71 // the value the verdict was made on }, "account_assessment": { "type": "behavior-drift/2", "account_id": "user-42", "state": "assessed_alert", "verdict": { "severity": "high", "score": 0.71, "threshold": 0.3565, "scale": "manhattan-normalized", "basis": "ring", "windows": 9 }, "profile": { "windows": 6, "windows_effective": 5, "oldest_window_age_seconds": 1837 } } // …the remaining identification fields}لماذا يحمل type إصدارًا. تغيّر البناء الكامن خلف الدرجة من قبل: مقياس الحكم
وأساسه. وbehavior-drift/2 هو أول بناء ذي إصدار. فإن كنت قد عايرت قواعدك الخاصة على هذا
الرقم، فاعتبر تغيّر هذه القيمة إشارة إلى إعادة فحصها.
account_alert.drift_score وidentification.drift_score هما الرقم نفسه (منذ
2026-10-03): درجة السلوك المُنعَّمة التي صدر عليها الحكم، مقيسةً مقابل حلقة النوافذ
الموثوقة السابقة لصاحب الحساب. وقبل ذلك التاريخ كان identification.drift_score يحمل
مسافة خام على مقياس مختلف — لا تخلط القيم عبر هذا التاريخ. وعندما لا يُحكم على نافذة،
يغيب الحقل بدل أن يكون صفرًا. استخدم account_assessment.verdict حين تحتاج إلى الرقم مع
العتبة التي قورن بها.
في الإصدار 1 تكون الكتلة accountAlert مع type وlinkedId وdrift، ولا توجد كتلة
تقييم. وفي الإصدار 2 يعيد التنبيه تسمية حقلين — linkedId ← accountId وdrift ←
driftScore — ويضيف accountAssessment؛ ويستخدم الإصدار 3 أسماء snake_case الواردة
أعلاه. حدّث معالجك عند تبديل الإصدارات، وإلا فسيتوقف منطق الاستيلاء على الحسابات لديك عن
رؤية البيانات في صمت.
account_assessmentلخطتَي Business وEnterprise فقط. يتطلب اشتراكًا صريحًا: * لا يشمله.
من دون هذا الحدث، يعني غياب التنبيه أمرين مختلفين في آنٍ واحد: إما أننا قيّمنا الجلسة مقابل ملف صاحب الحساب فتطابقت، وإما أننا لم نستطع تقييمها إطلاقًا — فملف صاحب الحساب غير جاهز، أو النافذة كانت شحيحة جدًا، أو الحركة لم تكن لشخص. والأمران يبدوان صمتًا، والصمت يُقرأ أمانًا. وهو في الغالب ليس كذلك: ففي يوم إنتاج كامل في سبتمبر 2026 امتنعنا عن إصدار حكم على 73% من النوافذ و80% من الحسابات. والعملية المبنية على أن «لا تنبيه يعني لا مشكلة» مبنية على حالة لا نملكها في الغالب.
{ "version": 3, "event": "account_assessment", "event_id": "9c1f6a2e-3b7d-4c58-a1e2-6f0d8b4a7c31:ato-state-not_assessed", "request_id": "9c1f6a2e-3b7d-4c58-a1e2-6f0d8b4a7c31", "phase": "beacon", "account_assessment": { "type": "behavior-drift/2", "account_id": "user-42", "state": "not_assessed", // always present, one of three values "reason": "profile_not_ready", // only when state is not_assessed "profile": { "windows": 3, // windows in the owner's reference ring "windows_effective": 2, // of those, how many yielded a distance "oldest_window_age_seconds": 1837 } } // …the remaining identification fields}state لا يغيب أبدًا ولا يكون فارغًا أبدًا:
state | المعنى | ما العمل |
|---|---|---|
assessed_clear | حُكم عليه؛ السلوك يطابق صاحب الحساب | لا شيء |
assessed_alert | حُكم عليه؛ السلوك منحرف | تحقّق إضافي (step-up) — ويحمل تنبيه account_takeover الحكم نفسه |
not_assessed | لم يصدر حكم | لا تعامله على أنه سليم؛ ارجع إلى إشاراتك الخاصة |
reason (مع not_assessed فقط):
reason | المعنى |
|---|---|
profile_not_ready | مرجع صاحب الحساب لا يستطيع حاليًا إصدار حكم لهذا الحساب. تعامل معه بوصفه حالة لا مرحلة: فقد لا يتغيّر أبدًا، فلا تنتظر |
profile_learning | المرجع لا يزال قيد التجميع |
window_too_sparse | كان هناك شخص، لكن النافذة حملت تفاعلًا قليلًا جدًا |
not_computable | لم يكن للنافذة والمرجع محاور مفيدة مشتركة |
bot_traffic | كانت البيانات كافية، ولم يكن هناك شخص للحكم عليه |
منذ 2026-10-04 18:00 UTC لم يعد window_too_sparse وnot_computable يصدران لصاحب حساب
مرجعه جاهز — فمثل هذه النافذة يُبلَّغ عنها بوصفها profile_not_ready. ويبقى السببان في
القائمة لأن عمليات التسليم السابقة تحملهما: فإن كان معالجك يتفرّع بناءً عليهما، فوجِّه
profile_not_ready بالطريقة نفسها، ولا تقارن معدلات هذين السببين عبر ذلك التاريخ.
state وreason غير مقيّدين بالخطة. أما verdict فلخطط Business+، مثل drift_score.
كتلة verdict موجودة فقط مع assessed_clear وassessed_alert. ومع not_assessed
تغيب الكتلة بدل أن تُملأ بأصفار — فـ score بقيمة 0 بجانب threshold بقيمة 0 سيُقرأ
«تطابق السلوك تمامًا».
| الحقل | المعنى |
|---|---|
severity | low أو high — نطاقان عن قصد: فالنطاق الأوسط يحتاج إلى عتبة ثانية على المقياس نفسه |
score | القيمة التي صدر عليها الحكم |
threshold | القيمة التي قورنت بها فعليًا |
scale | كيف تُبنى الدرجة: manhattan-normalized هي d/(1+d) في فضاء سلوكي مُطبَّع، حيث يعني 0 تطابقًا تامًا |
basis | ring — المسافة إلى أقرب نافذة من النوافذ الموثوقة السابقة لصاحب الحساب؛ centroid — المسافة إلى ملفه المتوسَّط |
windows | كم نافذة جُمعت في الدرجة |
يقع score وthreshold دائمًا على المقياس المسمّى نفسه، فالزوج يعيد إنتاج القرار: يُطلق
التنبيه بالضبط حين يكون score > threshold.
كم مرة يصل. عملية تسليم واحدة على الأكثر لكل حالة لكل زيارة — وعمليًا واحدة لكل زيارة
تحمل linked_id، لا واحدة لكل نافذة سلوكية. وقد تتغيّر الحالة داخل زيارة واحدة — مثلًا،
ينتهي المرجع من التعلّم فيصبح not_assessed هو assessed_clear — وعندها تتلقّى الاثنين
بالترتيب. وهذا الانتقال ممكن لا موعود: فقد يستمر profile_not_ready طوال عمر الحساب.
ما لا يعد به. أفضل الحسابات حمايةً هي حسابات المستخدمين النشطين ذوي الجلسات الطويلة وملف المرجع الكامل؛ وأسوأها حمايةً حسابات المستخدمين غير المنتظمين، الذين يكون الاستيلاء على حساباتهم أخطر. وهذه خاصية من خصائص البناء، لا عتبة يمكن ضبطها. فالحدث يبلّغ عن انحراف سلوكي عن مرجع — وليس ادّعاءً برصد الاستيلاء على الحسابات عمومًا.
attack_detectedيصل تنبيه ارتفاع البوتات في مغلّف v3 المشترك مع كتلة attack. وليس له حقول تعرّف — فهذا
حدث على مستوى مساحة العمل:
{ "version": 3, "event": "attack_detected", "event_id": "c0a8e1f2-…", // alert id; matches X-Tracio-Event-Id "timestamp": "2026-07-30T12:00:00Z", "attack": { "kind": "bot_spike", "severity": "critical", // warning | critical "window_minutes": 15, "recent_bots": 4210, "recent_total": 5100, "expected": 180.5 // the baseline expected over a window this size }}تتلقّى الـ webhooks على الإصدار 1 من الحمولة مخطط التنبيه المسطّح التاريخي — وهو لا يتغيّر.
login_risk وtrusted_device_enrolled وtrusted_device_revokedتأتي هذه الأحداث الثلاثة من استدعاءاتك أنت لواجهة مخاطر تسجيل الدخول
(POST /v2/logins/assess) ولأجهزتها الموثوقة. ولا تُسلَّم إلا عند اشتراك صريح
بالاسم — فـ * لا يشملها — وفقط إلى الـ webhooks على الإصدار 2 أو 3 من الحمولة؛
فالإصدار 1 لا جسم له لها.
login_risk حين يعيد التقييم challenge أو review — ولا يُرسَل أبدًا عند
allow أو deny أو not_assessed. وكتلة login_risk فيه هي التقييم المخزَّن، تمامًا
كما أعطته الاستجابة المتزامنة. معرّف الحدث: login-risk:<id>، حيث <id> هو معرّفنا
للسجلّ المخزَّن — خذ assessment_id الخاص بك من الجسم.trusted_device_enrolled وtrusted_device_revoked عند كل تسجيل لجهاز موثوق وكل
تجديد وكل إلغاء. وكتلة trusted_device فيهما — user_id وdevice_ref وenrolled_at
وexpires_at وrevoked_at وrevoked_by (api أو dashboard) — لقطة لحظة الحدث؛ ومع
trusted_device_enrolled تكون revoked_at وrevoked_by بقيمة null. معرّف الحدث:
trusted-device:<n>.تُرسَل بشكل غير متزامن، بعد الاستدعاء بما يصل إلى خمس ثوانٍ تقريبًا، ولا تحمل ترويسة
X-Tracio-Request-Id — استخدم login_risk.request_id للعثور على الزيارة. والـ webhook
إشعار للتدقيق والتنبيه؛ أما القرار نفسه فهو الاستجابة المتزامنة.
{ "version": 3, "event": "login_risk", "event_id": "login-risk:4b1f9e2c-6d3a-4f80-9b7e-2a5c8d1e0f37", "timestamp": "2026-10-17T10:00:00Z", "login_risk": { "assessment_id": "snap-1", "user_id": "alice", "state": "assessed", "recommendation": "challenge", "reasons": ["new_device"] // …the rest of the assessment }}{ "version": 3, "event": "trusted_device_revoked", "event_id": "trusted-device:7", "timestamp": "2026-10-17T11:00:00Z", "trusted_device": { "user_id": "alice", "device_ref": "7978151c54e94838", "enrolled_at": "2026-10-17T10:00:00Z", "expires_at": "2026-11-16T10:00:00Z", "revoked_at": "2026-10-17T11:00:00Z", "revoked_by": "dashboard" }}affiliate_threshold_crossedلا يُسلَّم إلا عند اشتراك صريح بالاسم — فـ * لا يشمله — وفقط إلى الـ webhooks على
الإصدار 2 أو 3 من الحمولة. كل 15 دقيقة يعيد TRACIO حساب درجة الجودة لكل شريك تسويق
بالعمولة لديه بيانات كافية، ويرسل الحدث حين تسوء توصيته: none → review أو none → hold
أو review → hold. أما التحسّن فلا يُرسل شيئًا ويتيح الإبلاغ عن التدهور التالي مجددًا.
والحدث يخص مساحة العمل: فهو يذهب إلى الـ webhooks المشتركة في كل بيئة ولا يحمل ترويسة
X-Tracio-Request-Id. معرّف الحدث:
affiliate-threshold:<affiliate_id>:<recommendation>:<unix time>.
{ "version": 3, "event": "affiliate_threshold_crossed", "event_id": "affiliate-threshold:aff_17:review:1792490400", "timestamp": "2026-10-20T10:00:00Z", "affiliate_threshold": { "affiliate_id": "aff_17", "recommendation": "review", // none | review | hold "previous_recommendation": "none", "quality_score": 72, // 0..100, higher is better "hold_days": 14, // the current hold period of the workspace "crossed_at": "2026-10-20T10:00:00Z", "metrics": { "reg_to_ftd": 0.31, "active14_share": 0.42, "dup_device_share": 0.3, "dup_person_share": 0.1, "ring_share": 0.05, "bonus_hunter_share": 0.2, "dc_vpn_bot_share": 0.1, "chargeback_share": 0.03, "affiliate_fraud_share": 0, "no_visit_share": 0.05 } }}metrics هي حصص شريك التسويق بالعمولة على مدى آخر 90 يومًا، وهي نفسها التي يعرضها تقرير
لوحة التحكم: حصة التسجيلات التي بلغت أول إيداع (reg_to_ftd)، وحصة اللاعبين الذين ظلوا
نشطين بعد 14 يومًا من التسجيل (active14_share)، وحصص التحويلات ذات الجهاز المكرّر أو
الشخص المكرّر، أو الآتية من حلقة احتيال، أو من صائدي المكافآت، أو من مركز بيانات أو VPN أو
بوت، أو الموسومة بوصفها رد مبالغ (chargeback) أو احتيال تسويق بالعمولة، أو التي لم يُعثر
لها على زيارة. TRACIO يوصي فقط — أما الحجز نفسه وأي قرار بشأن الدفع فيبقيان في منصة
التسويق بالعمولة لديك. وفي الإصدار 2 من الحمولة يحمل الجسم نفسه مفاتيح camelCase: الكتلة
هي affiliateThreshold، مع affiliateId وpreviousRecommendation وqualityScore
وهكذا.
transaction_state_changedلخطة Business وما فوقها. يُرسَل حين تتغيّر نتيجة معاملة أرسلتها إلى
POST /v2/transactions: قررت القواعد REVIEW أو DECLINE (أما APPROVE فلا يرسل
شيئًا)، أو أرسل محلّل المعاملة إلى المراجعة في لوحة التحكم، أو عُيِّن وسم أو استُبدل أو
أُزيل. استخدمه لحجز مكافأة ما دامت مطالبة قيد المراجعة، ولحجبها أو الإفراج عنها متى
استقرت النتيجة، دون استطلاع دوري. و* يشمل هذا الحدث؛ ولا يُسلَّم إلا إلى الـ webhooks
على الإصدار 2 أو 3 من الحمولة. والـ webhook المرتبط ببيئة لا يتلقّاه إلا لتلك البيئة.
{ "version": 3, "event": "transaction_state_changed", "event_id": "txn-state:7d1c2b3a-0000-4000-8000-000000000042:118", "timestamp": "2026-10-15T10:04:00Z", "transaction": { "id": "7d1c2b3a-0000-4000-8000-000000000042", "transaction_id": "bonus-claim-1842", "state": "REVIEW", // always the decision of the rules "fraud_score": 50, "effective_state": "DECLINE", // the outcome to act on "recommendations": ["withhold_bonus"], "label": "fraud_detection_bonus_abuse", "label_source": "api", // api | dashboard "label_at": "2026-10-15T10:04:00Z", "previous_effective_state": "REVIEW" // …applied_rules and evidence, as in the transaction response }}| المفتاح | المعنى |
|---|---|
effective_state | النتيجة التي يُبنى عليها التصرّف: حالة المحلّل أو حالة الوسم عند تعيين أحدهما، وإلا فـ state |
recommendations | ما العمل بالمكافأة، مشتقًا من effective_state |
label, label_source, label_at | الوسم الحالي، ومكان تعيينه (api أو dashboard) ووقته؛ والثلاثة كلها null حين لا يكون للمعاملة وسم |
previous_effective_state | effective_state قبل هذا التغيير؛ وnull للحدث المرسَل عند القرار التلقائي |
تُظهر الكتلة المعاملة كما هي لحظة إرسال الحدث: فإن تلاحق تغييران بسرعة، حمل الحدثان
كلاهما أحدث حالة، ولكلٍّ منهما previous_effective_state وtimestamp الخاصان به.
الترتيب غير مضمون — رتّب حسب timestamp، واقرأ المعاملة عبر API لمعرفة حالتها
الراهنة. معرّف الحدث: txn-state:<id>:created للقرار التلقائي وtxn-state:<id>:<n> لكل
تغيير لاحق. ولا يقول الحدث من عيّن الوسم — بل من أين جاء فقط.
reputation_changedلخطة Business وما فوقها. يُرسَل حين تنتقل السمعة العابرة للعملاء لشخص رأته مؤسستك إلى
نطاق آخر (clean ← suspicious، مثلًا). ويتطلب طبقة الشخص، التي تعمل حاليًا في وضع
المراقبة — ولذلك لا يُطلق الحدث إلا في مساحات العمل التي جرى فيها تفعيل تحديد الهوية عبر
الأجهزة. وهذا حدث على مستوى الشخص: ليس له request_id ولا كتل زيارة.
{ "version": 3, "event": "reputation_changed", "event_id": "person-reputation:pn_4f2a…:clean->suspicious:2026-07-30T12:10:00.123456Z", "timestamp": "2026-07-30T12:10:00Z", "reputation_change": { "person_id": "pn_4f2a…", "score": 46, "band": "suspicious", // clean | suspicious | fake "previous_band": "clean", "categories": ["chargeback"], "visitor_ids": ["X7fh2Hg9LkMn3pQr5tBvQw3xZa9mK2pL4nR8dT6y"], // the person's visitors in YOUR data "linked_accounts_count": 4 // omitted when zero }}مفتاح الكتلة هو reputation_change لا reputation: فالأخير محجوز للسمعة في سياق زيارة
بعينها، والاثنان أمران مختلفان. ويسلّم المخطط 1 الصيغة المسطّحة التاريخية — الحقول نفسها
في المستوى الأعلى.
تتضمن كل عملية تسليم ترويسة X-Tracio-Signature:
X-Tracio-Signature: t=1710432000,v1=5257a869e7ecebed…t هو طابع الوقت بنظام يونكس (بالثواني) لحظة توقيع الطلب.v1 هو HMAC-SHA256 لـ "<t>.<rawRequestBody>" بترميز ست عشري، بمفتاح هو سرّ
الـ webhook الخاص بك.طابع الوقت جزء من المحتوى الموقَّع، وهذا ما يمنح الحماية من إعادة التشغيل.
أمران يجب ضبطهما، وإلا فشل التحقّق في بيئة الإنتاج:
v1=. فأثناء تدوير السرّ تحمل الترويسة توقيعَين
اثنين، وأي محلّل يحتفظ بواحد منهما فقط سيرفض عمليات تسليم صحيحة طوال نافذة
التدوير.// Express.js exampleimport express from "express"import crypto from "crypto"
const app = express()
// Capture the raw body so the signature can be verified byte-for-byte.app.use( express.json({ verify: (req, _res, buf) => { ;(req as any).rawBody = buf }, }),)
function verifySignature(rawBody: Buffer, header: string, secret: string): boolean { if (!header) return false
const parts = header.split(",").map((p) => p.trim()) const ts = parts.find((p) => p.startsWith("t="))?.slice(2) if (!ts) return false
// Replay protection: reject timestamps more than five minutes old. if (Math.abs(Date.now() / 1000 - Number(ts)) > 300) return false
// Sign the raw bytes: the "<t>." prefix plus the raw request body. const signed = Buffer.concat([Buffer.from(`${ts}.`, "utf8"), rawBody]) const expected = crypto.createHmac("sha256", secret).update(signed).digest("hex") const exp = Buffer.from(expected, "hex")
// During a rotation window the header carries several v1= — any may match. return parts.some((p) => { if (!p.startsWith("v1=")) return false const got = Buffer.from(p.slice(3), "hex") // Compare lengths BEFORE timingSafeEqual: it throws on differing lengths, // and one junk header would turn the handler into a 500. return got.length === exp.length && crypto.timingSafeEqual(got, exp) })}
app.post("/webhook/tracio", (req, res) => { const header = req.headers["x-tracio-signature"] as string if (!verifySignature((req as any).rawBody, header, WEBHOOK_SECRET)) { return res.status(401).json({ error: "Invalid signature" }) }
const event = req.body console.log(`Visitor: ${event.visitor_id}`) console.log(`Bot: ${event.bot?.result}`) // "human" | "bot" | "uncertain"
res.status(200).send("OK")})تحمل عمليات التسليم وفق المخططين 2 و3 إضافةً إلى ذلك X-Tracio-Signature-Ed25519
(t=<unix>,kid=<id>,v1=<base64>). كلا الطرفين يعرف سرّ HMAC، لذا فإن HMAC يثبت أن
المرسِل يعرف السرّ، لا أن TRACIO هي مصدر الطلب؛ والتوقيع غير المتماثل هو ما يثبت ذلك.
وتُنشر المفاتيح العامة على https://api.tracio.ai/.well-known/webhook-keys مفهرسةً
بـ kid: اقرأ الحقل public_key. ويحمل الاسم البديل القديم publicKey القيمة نفسها
وسيُزال في 2027-04-12.
أما عمليات التسليم التجريبية المرسَلة من لوحة التحكم فتُوقَّع بـ HMAC فقط — إذ يعيش
المفتاح الخاص للمنصة على عُقد التسليم وهو غير متاح للوحة التحكم عن قصد. وأي مدقّق
يشترط Ed25519 اشتراطًا صارمًا عليه أن يمرّر عمليات التسليم التجريبية (فهي تحمل اللاحقة
:test في event_id)، وإلا فشل الاختبار من لوحة التحكم بينما بيئة الإنتاج سليمة تمامًا.
ويسري الحذر نفسه على فحوص الشكل: فعملية التسليم التجريبية تحمل request_id على صورة
test_<hex> وتحمل الحرفية test_visitor بوصفها visitor_id، فالمعالج الذي يتحقّق من
هذين المعرّفين مقابل صيغتَي الإنتاج سيرفض عملية تسليم سليمة البنية فيما عدا ذلك.
بعد التدوير يظل السرّان صالحَين لمدة 24 ساعة وتحمل الترويسة كلا التوقيعَين، فيمكنك تحديث إعداداتك دون فقدان عمليات تسليم. ويقصّر إجراء Revoke now هذه النافذة. حدّث السرّ لديك خلال 24 ساعة: فبمجرد إغلاق النافذة يتوقف السرّ القديم عن المطابقة، وإذا كانت نقطة النهاية لديك تردّ على التوقيع غير الصالح بالرمز 4xx، فإن خمس استجابات كهذه على التوالي تُعطّل الـ webhook.
| الترويسة | الوصف |
|---|---|
Content-Type | application/json |
X-Tracio-Signature | t=<unix>,v1=<hmac_sha256_hex> — قيمتا v1= أثناء نافذة التدوير |
X-Tracio-Signature-Ed25519 | توقيع المنصة، t=<unix>,kid=<id>,v1=<base64> (الإصداران 2 و3 فقط) |
X-Tracio-Event-Id | معرّف التسليم — مفتاح idempotency |
X-Tracio-Request-Id | معرّف الزيارة، يطابق request_id (الإصداران 2 و3 فقط). لا يُرسَل إلا لأحداث الزيارة — identification وaccount_takeover وaccount_assessment |
X-Tracio-Event-Type | نوع الحدث (الإصداران 2 و3 فقط) |
X-Tracio-Delivery-Attempt | رقم المحاولة، بدءًا من 1 (الإصداران 2 و3 فقط) |
X-Tracio-Payload-Version | 3 أو 2 (الإصداران 2 و3 فقط) |
X-Tracio-Webhook-Id | معرّف الـ webhook الذي أنتج عملية التسليم هذه |
قد تُعاد محاولة التسليم، وتحمل المحاولة المعادة قيمة X-Tracio-Event-Id نفسها. أزل
التكرار بناءً عليها:
app.post("/webhook/tracio", async (req, res) => { const eventId = req.headers["x-tracio-event-id"] as string
const existing = await db.webhooks.findOne({ eventId }) if (existing) return res.status(200).send("Already processed")
await db.webhooks.insert({ eventId, processedAt: new Date() }) await processWebhookEvent(req.body)
res.status(200).send("OK")})لاحظ أن event_id فريد لكل حدث، لا لكل webhook: فإذا اشترك عدة webhooks في مساحة
العمل بالحدث نفسه، تلقّى كل منها عملية تسليم بالمعرّف ذاته. وبالنسبة إلى identification
يُبنى على صورة <request_id>:<phase>، ولهذا تُزال التكرارات لمراحل الزيارة الثلاث كلٌّ على
حدة بدل أن تنطوي في واحدة. وللأحداث التي لا زيارة لها صيغها الخاصة — راجع كل حدث أعلاه.
استجب بالرمز 2xx — فهو العلامة الوحيدة على قبول عملية التسليم.
| الاستجابة | ماذا يحدث |
|---|---|
2xx | اكتمل التسليم |
429 Too Many Requests | لا تُحتسب فشلًا ولا تستهلك محاولة؛ ويُحترم Retry-After الأطول |
408 و425 و5xx وانقطاع الاتصال | تُعاد المحاولة مع فترة انتظار متزايدة |
410 Gone | تُعامَل نقطة النهاية كأنها أُزيلت — ويُعطَّل الـ webhook فورًا |
بقية رموز 4xx | تُعاد المحاولة، لكن خمسًا متتالية تُعطّل الـ webhook — و400/401/404 لا تُعالَج بالإعادة |
جدول إعادة المحاولات: 5 ثوانٍ ← 30 ثانية ← دقيقتان ← 10 دقائق ← 30 دقيقة ← ساعتان ← 6 ساعات (8 محاولات). المحاولات الأولى تقع داخل دقيقة واحدة، لذا لا تكلّفك إعادة تشغيل قصيرة لخدمتك أي إشعار. وتُوزَّع كل فترة انتظار عشوائيًا بين نصف القيمة وقيمتها الكاملة حتى لا تنطلق المحاولات دفعة واحدة بعد انقطاع.
التعطيل التلقائي يتطلب في آنٍ واحد بلوغ عتبة (20 إخفاقًا متتاليًا، أو 5 أخطاء إعداد) و 15 دقيقة متواصلة على الأقل من الإخفاقات — فإعادة تشغيل قصيرة لا يمكن أن تقتل التكامل حتى لو تراكمت عمليات تسليم كثيرة في الطابور. وأي فجوة تتجاوز 15 دقيقة تعيد العدّ من الصفر. وتعرض لوحة التحكم السبب مع رمز الاستجابة ونص الخطأ، وزرّ Re-enable الذي يصفّر العدّادات.
| الخطة | عدد الـ webhooks لكل مساحة عمل |
|---|---|
| Free | غير متاح |
| Pro | 5 |
| Business | 20 |
| Enterprise | 100 |
يجب أن تكون نقاط النهاية على https وبعنوان IP عام — إذ تُرفض العناوين الخاصة وعناوين
الاسترجاع (loopback)، بما في ذلك عند إعادة التوجيه — وبعمق لا يتجاوز إعادتَي توجيه.
لا تُتَّبع سوى إعادتَي التوجيه 307 و308. أما 301 و302 و303 فتوجّه العميل
إلى التحول إلى GET وإسقاط الجسم، لذا لا تتبعها عملية التسليم وتُحتسب المحاولة فاشلة.
وإذا كان موازن الحمل لديك يطبّع العنوان (بإضافة www أو شرطة مائلة في النهاية)، فوجّه
الـ webhook مباشرة إلى العنوان النهائي.
تُدار الـ webhooks من لوحة التحكم. وتشغّل لوحة التحكم واجهة API للإدارة على نطاق
مساحة العمل، تُقدَّم على مضيف التطبيق (مثلًا https://app.tracio.ai/api/v1)، ونقاط
النهاية أدناه هي بالضبط ما تستدعيه. وتقع كل نقاط نهاية الـ webhooks تحت
/workspaces/{wsId}.
هذه ليست واجهة للتعامل بين الخوادم. فواجهة الإدارة لا تقبل سوى رمز JWT الخاص بـ جلسة لوحة التحكم، ويُفحَص مقابل دورك في مساحة العمل (RBAC)؛ أما المفتاح السرّي
tracio_sk_…فمرفوض هنا. وبما أن هذه الجلسة تعيش في المتصفح وتنتهي بانتهائه، فاعتبر الاستدعاءات أدناه وصفًا لما تفعله لوحة التحكم لا تكاملًا تُؤتمته. وللوصول البرمجي من خادمك الخلفي، استخدم Data API للقراءة فقط.
curl -X POST https://app.tracio.ai/api/v1/workspaces/{wsId}/webhooks \ -H "Authorization: Bearer <session-jwt>" \ -H "Content-Type: application/json" \ -d '{ "url": "https://your-server.com/webhook/tracio", "events": [] }'سرّ التوقيع يولّده TRACIO ويُعاد مرة واحدة عند الإنشاء (وعند التدوير) تحت
signingSecret. خزّنه بأمان — فهو المفتاح الذي تتحقق به من التواقيع.
{ "ok": true, "data": { "id": "b3d4f8a1-2c67-4e9b-8f05-7a1d3c9e2b48", "workspaceEnvironmentId": "b201f2ba-…", "url": "https://your-server.com/webhook/tracio", "events": [], "signingSecret": "f3a9…<hex>", "status": "active", "successRate": 100, "createdAt": "2026-07-30T12:00:00Z" }}وفي القراءات اللاحقة يكون signingSecret مقنَّعًا (null) — ولا يُكشف إلا عند
الإنشاء وعند تدوير السرّ.
| الطريقة | المسار | الوصف |
|---|---|---|
GET | /workspaces/{wsId}/webhooks | سرد الـ webhooks |
PATCH | /workspaces/{wsId}/webhooks/{webhookId} | تحديث url / events / status |
DELETE | /workspaces/{wsId}/webhooks/{webhookId} | حذف webhook |
POST | /workspaces/{wsId}/webhooks/{webhookId}/test | إرسال تسليم تجريبي موقَّع |
POST | /workspaces/{wsId}/webhooks/{webhookId}/secret/rotate | تدوير سرّ التوقيع |
GET | /workspaces/{wsId}/webhooks/{webhookId}/deliveries | سرد محاولات التسليم الأخيرة |
أعِد استجابة 2xx بأسرع ما يمكن وعالِج الحمولة بشكل غير متزامن لتجنّب انتهاء المهلة:
app.post("/webhook/tracio", async (req, res) => { res.status(200).send("OK") processWebhookEvent(req.body).catch(console.error)})
async function processWebhookEvent(event: WebhookPayload) { await db.events.insert(event)
if (event.decision?.risk_score > 50) { await alertFraudTeam(event) }
if (event.bot?.result === "bot") { await blockVisitor(event.visitor_id) }}استخدم إجراء Test على أحد الـ webhooks (أو
POST .../webhooks/{webhookId}/test) لإرسال حمولة نموذجية موقَّعة إلى نقطة النهاية
لديك والتأكد من أنها قابلة للوصول وتتحقق من التواقيع بشكل صحيح.
وللتطوير المحلي، أتِح خادمك عبر نفق مثل ngrok:
ngrok http 3000# Use the generated URL as your webhook endpoint