Webhooks पहचान इवेंट को रियल-टाइम में आपके सर्वर तक पहुँचाते हैं। हर बार जब कोई
विज़िटर पहचाना जाता है, TRACIO आपके कॉन्फ़िगर किए गए webhook URL पर एक HTTP POST
अनुरोध भेजता है। अनुरोध का बॉडी ही इवेंट पेलोड है।
ये देर से आने वाले वर्डिक्ट देने वाला एकमात्र चैनल भी हैं — यानी वे, जहाँ पेज लोड हो जाने के बाद विज़िटर के व्यवहार ने साबित किया कि वह स्वचालित था।
इन्हें डैशबोर्ड में Settings → Webhooks के अंतर्गत सेट करें। Webhooks के लिए Pro प्लान या उससे ऊपर आवश्यक है।
| इवेंट | कब | प्लान |
|---|---|---|
identification | हर विज़िट पर — primary, late और correction फ़ेज़ | सभी |
account_takeover | किसी खाते के अंतर्गत व्यवहार अब मालिक की प्रोफ़ाइल से मेल नहीं खाता | Business और उससे ऊपर |
attack_detected | आपकी साइट पर बॉट्स का उछाल | Business और उससे ऊपर |
reputation_changed | किसी डिवाइस के पीछे मौजूद व्यक्ति की प्रतिष्ठा बदल गई | Business और उससे ऊपर |
इवेंट के नाम अंडरस्कोर से बनते हैं, कभी डॉट से नहीं — visitor.created या
session.created जैसा कुछ नहीं होता। reputation_changed के लिए पर्सन लेयर आवश्यक
है, इसलिए यह केवल उन्हीं वर्कस्पेस में ट्रिगर होता है जहाँ क्रॉस-डिवाइस पहचान समाधान
सक्षम है।
एक webhook विशिष्ट प्रकारों की सदस्यता लेता है; अलग मान * का अर्थ है "हर प्रकार,
जिसमें बाद में जोड़े गए भी शामिल हैं"। सदस्यता बनाते या संपादित करते समय अज्ञात प्रकार
को 400 के साथ अस्वीकार किया जाता है, ताकि किसी टाइपो के कारण आपके पास ऐसा webhook न
रह जाए जो चुपचाप कभी ट्रिगर ही न हो।
identification इवेंट के फ़ेज़एक ही विज़िट से अधिकतम तीन डिलीवरी बनती हैं, जो एक ही requestId साझा करती हैं:
primary — पेज लोड पर आया प्रारंभिक वर्डिक्ट।late — लगभग नौ सेकंड बाद, जब धीमी जांचें पूरी हो जाती हैं, तब का एनरिचमेंट।correction — व्यवहार (पॉइंटर, कीबोर्ड, स्क्रॉलिंग) पर आधारित सुधार।इन्हें requestId से सहसंबंधित करें और phase से अलग पहचानें। बाद वाला फ़ेज़
प्राथमिक होता है: यदि primary ने human कहा और correction कहता है bot, तो सही
उत्तर दूसरा है।
आगमन के क्रम पर निर्भर न रहें। हर फ़ेज़ स्वतंत्र रूप से और अपने अलग रिट्राई शेड्यूल
पर डिलीवर होता है — यदि primary रिट्राई में चला गया जबकि late पहली ही कोशिश में
सफल हो गया, तो आपको वे उल्टे क्रम में मिलेंगे। प्राथमिकता प्राप्ति के समय से नहीं,
phase फ़ील्ड से तय करें।
identification इवेंट के फ़ेज़ बस यही तीन हैं। एक और मान आप तक पहुँचता है:
account_takeover phase: "beacon" लाता है, क्योंकि खाता कब्ज़े का अलर्ट हमेशा
व्यवहार-बीकन से ही उठाया जाता है।
ध्यान दें कि इससे एक बेमेल बनता है, और यह आइडेम्पोटेंसी पर असर डालता है। प्रोडक्शन में
किसी identification डिलीवरी का eventId ठीक <requestId>:<phase> होता है, पर दो
डिलीवरी इस सूत्र को तोड़ती हैं। account_takeover में यह <requestId>:ato होता है —
सफ़िक्स अक्षरशः ato है, phase फ़ील्ड का मान नहीं। डैशबोर्ड से भेजी गई टेस्ट डिलीवरी
में यह <requestId>:test होता है, जबकि उसके स्कीमा 2 बॉडी में phase फिर भी primary
दिखाता है — और स्कीमा 1 बॉडी में phase फ़ील्ड होता ही नहीं, इसलिए वह सफ़िक्स केवल
हेडर में ही दिखाई देता है। eventId को सीधे आइडेम्पोटेंसी कुंजी के रूप में उपयोग करें
और उसे कभी requestId तथा phase से दोबारा न जोड़ें। जिन मानों को आप संभालते हैं उन
पर मिलान करें और बाकी को डिलीवरी अस्वीकार करने के बजाय अनदेखा करें।
attack_detected वर्कस्पेस-स्तर का इवेंट है: इसमें न requestId होता है, न
visitorId, और न ही browser, geo, bot या decision ब्लॉक — ये कुंजियाँ बस
मौजूद नहीं होतीं। account_takeover किसी विशिष्ट विज़िट से बनता है और आपके प्लान के
अनुसार पूरा पहचान बॉडी साथ लाता है, साथ में एक accountAlert ब्लॉक। यदि आप सभी इवेंट
एक ही हैंडलर में पार्स करते हैं, तो विज़िट फ़ील्ड्स को छूने से पहले event जांचें।
| वर्शन | किसके लिए | कैसे स्विच करें |
|---|---|---|
1 | v2 के अस्तित्व में आने से पहले बने webhooks | उनके लिए यही डिफ़ॉल्ट बना रहता है |
2 | नए webhooks | डैशबोर्ड में webhook कार्ड पर मौजूद टॉगल |
स्कीमा v1 फ़्रीज़ है — इसका कोई फ़ील्ड नहीं बदलता, इसलिए मौजूदा इंटीग्रेशन बिना बदलाव के काम करते रहते हैं। सब कुछ नया v2 में रहता है, और नए webhooks यही भेजते हैं।
{ "version": 2, "event": "identification", "eventId": "9c1f6a2e-3b7d-4c58-a1e2-6f0d8b4a7c31:primary", // "<requestId>:<phase>" — the idempotency key "requestId": "9c1f6a2e-3b7d-4c58-a1e2-6f0d8b4a7c31", // visit identifier, shared by all phases "phase": "primary", "visitorId": "X7fh2Hg9LkMn3pQr5tBvQw3xZa9mK2pL4nR8dT6y", "linkedId": "user-42", // your ?lid=, if you passed one "tag": "checkout", "timestamp": "2026-07-30T12:00:00Z", "url": "https://shop.example.com/checkout", "ip": "203.0.113.44", "userAgent": "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, "connectionType": "DCH", "proxyDetected": true // HTTP ट्रैफ़िक और कच्चा नेटवर्क पथ अलग-अलग नेटवर्कों से बाहर निकलते हैं }, "bot": { "result": "human", "score": 3.2 }, // human | bot | uncertain "identification": { "confidence": 0.97, "incognito": false }, "decision": { "action": "real", "riskScore": 12.2 } // real | fake | suspicious}शून्य और खाली मान हटा दिए जाते हैं। शून्य मान वाले स्ट्रिंग और संख्यात्मक फ़ील्ड
(जैसे किसी मानव के लिए bot.type) JSON में मौजूद नहीं होते — उन्हें अपने स्कीमा में
अनिवार्य न बनाएं, और नेस्टेड ब्लॉक सावधानी से पढ़ें।
bot.score और decision.riskScore 0..100 स्केल पर दशमलव हैं, दशमलव के बाद एक
अंक के साथ — ठीक वही संख्याएँ जो डैशबोर्ड उसी विज़िट के लिए दिखाता है। (फ़्रीज़ किए गए
v1 स्कीमा में इनकी इकाइयाँ अलग हैं: क्रमशः 0..1 का अंश और 0..255।)
bot.type या तो किसी पहचाने गए बॉट का नाम है, या एक परिवार का लेबल। शब्दावली के लिए
देखें बॉट के प्रकार — आंतरिक जांच के नाम किसी भी प्लान
पर कभी उजागर नहीं किए जाते।
| फ़ील्ड | प्रकार | विवरण |
|---|---|---|
version | number | पेलोड स्कीमा वर्शन (2) |
event | string | इवेंट प्रकार |
eventId | string | डिलीवरी पहचानकर्ता — आइडेम्पोटेंसी कुंजी |
requestId | string | विज़िट पहचानकर्ता (UUID), विज़िट के सभी फ़ेज़ों में साझा |
phase | string | primary, late, correction; account_takeover beacon लाता है |
visitorId | string | स्थिर विज़िटर पहचानकर्ता |
linkedId | string | क्लाइंट द्वारा दिया गया लिंक्ड पहचानकर्ता |
tag | string | क्लाइंट द्वारा दिया गया कस्टम टैग |
timestamp | string | इवेंट का समय (RFC 3339) |
url | string | वह पेज URL जहाँ इवेंट कैप्चर हुआ |
ip | string | क्लाइंट का IP पता |
userAgent | 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 (बूलियन) और connectionType |
network.proxyDetected | 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.riskScore | number | समग्र जोखिम स्कोर (0–100) |
Pro और उससे ऊपर — विज़िटर समय के साथ कैसा व्यवहार करता है:
{ "identification": { "matchType": "exact", // exact | fuzzy | new — how the visitor was recognized "matchConfidence": 0.93, "visits": 42, "incognitoVisits": 3 }, // Present when visitor counters are available at event time (usually primary). // A missing block means "no data", not "zeros". "velocity": { "events5m": 7, "uniqueIps": 2, "uniqueLocations": 1 }, "bot": { "antidetectScore": 0 }, // antidetect indicators, 0..100 "session": { "durationSeconds": 95 } // where the visit duration is already known}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 }, "identification": { "driftScore": 0.31 }, // divergence from the account profile "deviceInfo": { "deviceId": "…", // the physical device across browsers on it "crossBrowser": true, "confidence": 0.88, "linkedBrowsers": 3 }, "network": { "isp": "Deutsche Telekom", "asn": 3320, // कच्चे नेटवर्क पथ पर देखा गया सार्वजनिक पता, यानी प्रॉक्सी या VPN के पीछे का // पता; ऐसा कोई पता न दिखे तो अनुपस्थित "realIp": { "address": "203.0.113.7", "country": "NL", "isp": "KPN" } }, // Linux मशीन पर वास्तव में मापा गया डेस्कटॉप एनवायरनमेंट ("Mint 22+", // "Ubuntu", "GNOME", "KDE"); User-Agent किसी डिस्ट्रीब्यूशन को व्यक्त नहीं कर // सकता। निर्धारित न होने पर अनुपस्थित — अधिकांश Linux विज़िट में, और हर // ग़ैर-Linux विज़िट में। "osEnvironment": "Mint 22+", // विज़िट ने अपने बारे में क्या दावा किया, बनाम स्वतंत्र जाँचों ने क्या मापा। // केवल तभी मौजूद जब स्पूफ़िंग वाकई पकड़ी गई हो; ख़ाली `real` फ़ील्ड का अर्थ है // "जाँच चुप रही", कभी "पुष्ट" नहीं। `gpu` अक्ष पर `claimed.gpu` दावा किए गए // अडैप्टर को उसी पढ़ने योग्य मॉडल नाम से रखता है जो शीर्ष स्तर के `gpu` फ़ील्ड // में है। "spoofing": { "detected": true, "claimed": { "os": "Windows 10", "browser": "Chrome 139.0" }, "real": { "os": "macOS" }, "spoofedAxes": ["os", "screen"], // os | gpu | screen | network | browser "anonymousBrowser": { "detected": true, "names": ["Linken Sphere"] } }, // डिवाइस के तथ्य — विज़िटर का ब्राउज़र मशीन के बारे में क्या बताता है, हमारी ओर // से साफ़ किया हुआ। screen: रिज़ॉल्यूशन, कलर डेप्थ और device pixel ratio। // locale: ब्राउज़र की अपनी पसंदीदा भाषाएँ और उसका टाइमज़ोन — geo.timezone के // विपरीत, जो IP पते से निकाला जाता है; दोनों में बेमेल नकली लोकेशन का आम // संकेत है। clientHints: User-Agent Client Hints — CPU की आर्किटेक्चर और // बिटनेस, डिवाइस मॉडल (Android: `model` में मॉडल कोड, जैसे "SM-A556B", और // Google Play डिवाइस सूची से उसका मार्केटिंग नाम `deviceName` में, जैसे // "Samsung Galaxy A55 5G") और प्लेटफ़ॉर्म का सटीक संस्करण; इन्हें केवल // Chromium आधारित ब्राउज़र बताते हैं। विज़िट ऐसा डेटा न लाए तो हर ब्लॉक // अनुपस्थित रहता है, इसलिए हर एक को वैकल्पिक मानें। "screen": { "width": 2560, "height": 1600, "colorDepth": 30, "pixelRatio": 2 }, "locale": { "languages": ["en-US", "de"], "timezone": "Europe/Berlin" }, "clientHints": { "architecture": "arm", "bitness": "64", "platformVersion": "15.5.0" }, // केवल तभी मौजूद जब वीडियो अडैप्टर ने ख़ुद को वर्चुअल बताया हो; hypervisor एक // बंद शब्दकोश है (vmware, virtualbox, parallels, qemu, hyperv, bochs, // intel-gvt, vgpu)। ब्लॉक का न होना यह बताता है कि ऐसा कोई प्रमाण नहीं है। "environment": { "virtualMachine": true, "hypervisor": "vmware" }, "decision": { "suspectScore": 55 }, "guidance": { "version": 1, "overall": "review" } // see below}रीज़न-कोड शब्दावली और severity का अर्थ जानने के लिए देखें
बॉट डिटेक्शन।
guidance हर इंटीग्रेशन बिंदु के लिए "क्या करें" की तैयार सिफ़ारिशें लाता है, ताकि
आपको कच्चे स्कोर से नीति निकालनी न पड़े:
{ "guidance": { "version": 1, "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 | लेनदेन प्रोसेस न करें | खाता बनाने से मना करें | उन्हें अंदर न आने दें | कन्वर्ज़न का श्रेय न दें |
version नियम-सेट का वर्शन है — लॉजिक बेहतर होने पर इसे बढ़ाया जाता है। गाइडेंस
योगात्मक है: नए परिदृश्य नई कुंजियों के रूप में आते हैं और अनुबंध नहीं तोड़ते। बाद वाला
फ़ेज़ जीतता है, सिवाय आंशिक सलाह के: अधूरे इनपुट सेट पर गणना की गई डिलीवरी
"partial": true से चिह्नित होती है, और आंशिक सलाह उसी requestId के लिए पहले प्राप्त
पूर्ण सलाह को नहीं बदलती। सामान्य डिलीवरी में partial फ़ील्ड पूरी तरह अनुपस्थित
रहता है।
सटीक थ्रेशोल्ड जान-बूझकर दस्तावेज़ में नहीं दिए गए हैं। जिस सलाह से स्कोर की रिवर्स इंजीनियरिंग हो सके, वह सुरक्षा नहीं रह जाती।
account_takeover इवेंटकेवल Business और Enterprise। बॉडी आपके प्लान के अनुसार पूरा पहचान एनवेलप है, साथ में
एक accountAlert ब्लॉक, जो प्रति विज़िट अधिकतम एक बार भेजा जाता है:
{ "version": 2, "event": "account_takeover", "eventId": "9c1f6a2e-3b7d-4c58-a1e2-6f0d8b4a7c31:ato", "requestId": "9c1f6a2e-3b7d-4c58-a1e2-6f0d8b4a7c31", "phase": "beacon", // अलर्ट बीकन से उठाया जाता है; केवल eventId "ato" कहता है "accountAlert": { "type": "behavior-drift", "accountId": "user-42", // your linkedId for the account "driftScore": 0.83 } // …the remaining identification fields}v1 में यह ब्लॉक type, linkedId और drift लाता है; v2 में दो फ़ील्ड का नाम बदला गया
है — linkedId → accountId और drift → driftScore। payloadVersion स्विच करते
समय अपना हैंडलर अपडेट करें, वरना आपका अकाउंट-टेकओवर लॉजिक चुपचाप डेटा देखना बंद कर
देगा।
attack_detected इवेंट{ "version": 2, "event": "attack_detected", "eventId": "c0a8e1f2-…", "timestamp": "2026-07-30T12:00:00Z", "attack": { "kind": "bot_spike", "severity": "critical", // info | warning | critical "windowMinutes": 15, "recentBots": 4210, "recentTotal": 5100, "expected": 180.5 // the baseline expected over a window this size }}हर डिलीवरी में एक X-Tracio-Signature हेडर शामिल होता है:
X-Tracio-Signature: t=1710432000,v1=5257a869e7ecebed…t वह Unix टाइमस्टैम्प (सेकंड में) है जब अनुरोध पर हस्ताक्षर किए गए।v1 आपके webhook सीक्रेट से की-किया गया "<t>.<rawRequestBody>" का hex-एन्कोडेड
HMAC-SHA256 है।टाइमस्टैम्प हस्ताक्षरित सामग्री का हिस्सा है, जो रीप्ले सुरक्षा देता है।
दो बातें सही होनी चाहिए, वरना प्रोडक्शन में सत्यापन विफल हो जाएगा:
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.visitorId}`) console.log(`Bot: ${event.bot?.result}`) // "human" | "bot" | "uncertain"
res.status(200).send("OK")})स्कीमा 2 की डिलीवरी अतिरिक्त रूप से X-Tracio-Signature-Ed25519
(t=<unix>,kid=<id>,v1=<base64>) भी लाती हैं। HMAC सीक्रेट दोनों पक्ष जानते हैं,
इसलिए HMAC यह तो साबित करता है कि भेजने वाला सीक्रेट जानता है, पर यह नहीं कि अनुरोध
TRACIO से आया है; असममित सिग्नेचर यही साबित करता है। सार्वजनिक कुंजियाँ
https://api.tracio.ai/.well-known/webhook-keys पर kid के अनुसार प्रकाशित हैं।
डैशबोर्ड से भेजी गई टेस्ट डिलीवरी केवल HMAC से हस्ताक्षरित होती हैं — प्लेटफ़ॉर्म की
निजी कुंजी डिलीवरी नोड्स पर रहती है और जान-बूझकर डैशबोर्ड को उपलब्ध नहीं कराई जाती।
जो सत्यापनकर्ता Ed25519 को अनिवार्य करता है, उसे टेस्ट डिलीवरी को पास होने देना
चाहिए (उनके eventId पर :test सफ़िक्स होता है), वरना प्रोडक्शन स्वस्थ रहते हुए भी
डैशबोर्ड से टेस्टिंग विफल होगी। यही सावधानी फ़ॉर्मेट जांच पर भी लागू होती है: टेस्ट
डिलीवरी में requestId test_<hex> रूप में आता है और visitorId के रूप में अक्षरशः
test_visitor — इसलिए जो हैंडलर इन्हें प्रोडक्शन के रूपों के विरुद्ध जांचता है, वह ऐसी
डिलीवरी अस्वीकार कर देगा जो बाकी सब लिहाज़ से सही बनी हुई है।
रोटेशन के बाद दोनों सीक्रेट 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> (केवल v2) |
X-Tracio-Event-Id | डिलीवरी पहचानकर्ता — आइडेम्पोटेंसी कुंजी |
X-Tracio-Request-Id | विज़िट पहचानकर्ता (v2, केवल विज़िट इवेंट) |
X-Tracio-Event-Type | इवेंट का प्रकार (केवल v2) |
X-Tracio-Delivery-Attempt | प्रयास संख्या, 1 से शुरू (केवल v2) |
X-Tracio-Payload-Version | 2 (केवल v2) |
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")})ध्यान दें कि eventId प्रति इवेंट अद्वितीय है, प्रति webhook नहीं: यदि वर्कस्पेस के
कई webhooks एक ही इवेंट की सदस्यता लेते हैं, तो हर एक को उसी पहचानकर्ता के साथ डिलीवरी
मिलेगी। यह <requestId>:<phase> के रूप में बनता है, इसीलिए एक ही विज़िट के तीन फ़ेज़
आपस में मिलकर एक नहीं हो जाते, बल्कि स्वतंत्र रूप से डीडुप्लिकेट होते हैं।
2xx के साथ उत्तर दें — यही एकमात्र संकेत है कि डिलीवरी स्वीकार की गई।
| उत्तर | क्या होता है |
|---|---|
2xx | डिलीवरी पूरी |
429 Too Many Requests | विफलता नहीं गिनी जाती और कोई प्रयास खर्च नहीं होता; लंबा Retry-After माना जाता है |
408, 425, 5xx, कनेक्शन टूटना | बढ़ते हुए अंतराल के साथ दोबारा प्रयास |
410 Gone | एंडपॉइंट हटा हुआ माना जाता है — webhook तुरंत निष्क्रिय |
अन्य 4xx | दोबारा प्रयास होता है, पर लगातार पाँच webhook निष्क्रिय कर देते हैं — 400/401/404 रिट्राई से ठीक नहीं होते |
रिट्राई शेड्यूल: 5s → 30s → 2min → 10min → 30min → 2h → 6h (8 प्रयास)। पहले कुछ रिट्राई एक मिनट के भीतर हो जाते हैं, इसलिए आपकी सेवा के छोटे रीस्टार्ट से कोई सूचना नहीं खोती। हर अंतराल आधे और पूरे मान के बीच रैंडमाइज़ किया जाता है, ताकि किसी आउटेज के बाद सारे रिट्राई एक साथ न चलें।
ऑटो-डिसेबल के लिए थ्रेशोल्ड (लगातार 20 विफलताएँ, या 5 कॉन्फ़िगरेशन त्रुटियाँ) और कम से कम 15 मिनट लगातार विफलता — दोनों आवश्यक हैं; छोटा रीस्टार्ट इंटीग्रेशन को खत्म नहीं कर सकता, भले ही बहुत-सी डिलीवरी कतार में लग गई हों। 15 मिनट से लंबा अंतराल गिनती दोबारा शुरू कर देता है। डैशबोर्ड कारण दिखाता है, उत्तर कोड और त्रुटि पाठ के साथ, और एक Re-enable बटन देता है जो काउंटर रीसेट कर देता है।
| प्लान | प्रति वर्कस्पेस webhooks |
|---|---|
| Free | उपलब्ध नहीं |
| Pro | 5 |
| Business | 20 |
| Enterprise | 100 |
एंडपॉइंट सार्वजनिक IP वाले https होने चाहिए — निजी और लूपबैक पते अस्वीकार किए जाते
हैं, रीडायरेक्ट पर भी — और दो से अधिक रीडायरेक्ट गहरे नहीं।
केवल 307 और 308 रीडायरेक्ट का पालन किया जाता है। 301, 302 और 303 क्लाइंट
को GET पर स्विच करने और बॉडी हटाने का निर्देश देते हैं, इसलिए डिलीवरी उनका पालन नहीं
करती और वह प्रयास विफल गिना जाता है। यदि आपका लोड बैलेंसर URL को नॉर्मलाइज़ करता है
(www या अंत में स्लैश जोड़कर), तो webhook को सीधे अंतिम URL पर इंगित करें।
Webhooks का प्रबंधन डैशबोर्ड में होता है। डैशबोर्ड एक वर्कस्पेस-स्कोप्ड प्रबंधन API
चलाता है, जो एप्लिकेशन होस्ट पर उपलब्ध है (उदाहरण के लिए
https://app.tracio.ai/api/v1), और नीचे दिए गए एंडपॉइंट ठीक वही हैं जिन्हें वह कॉल
करता है। हर webhook एंडपॉइंट /workspaces/{wsId} के अंतर्गत है।
यह सर्वर-से-सर्वर सतह नहीं है। प्रबंधन API केवल आपके डैशबोर्ड सत्र का 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?.riskScore > 50) { await alertFraudTeam(event) }
if (event.bot?.result === "bot") { await blockVisitor(event.visitorId) }}किसी webhook पर Test क्रिया (या POST .../webhooks/{webhookId}/test) का उपयोग करके
अपने एंडपॉइंट पर हस्ताक्षरित नमूना पेलोड भेजें और पुष्टि करें कि वह पहुँच योग्य है और
सिग्नेचर सही ढंग से सत्यापित कर रहा है।
लोकल डेवलपमेंट के लिए, अपने सर्वर को ngrok जैसी टनल से एक्सपोज़ करें:
ngrok http 3000# Use the generated URL as your webhook endpoint