Webhooky doručují identifikační události na váš server v reálném čase. Pokaždé, když
je návštěvník identifikován, odešle TRACIO HTTP požadavek POST na vámi nastavenou
URL webhooku. Tělo požadavku je payload události.
Jsou zároveň jediným kanálem, který doručuje pozdní verdikty — ty, u nichž chování návštěvníka prokázalo automatizaci až po načtení stránky.
Nastavte je v dashboardu v sekci Settings → Webhooks. Webhooky vyžadují plán Pro nebo vyšší.
| Událost | Kdy | Plán |
|---|---|---|
identification | Při každé návštěvě — fáze primary, late a correction | Všechny |
account_takeover | Chování pod účtem už neodpovídá profilu jeho vlastníka | Business+ |
account_assessment | Stav posouzení převzetí účtu: posouzeno a v pořádku, posouzeno s odchylkou, nebo neposouzeno | Business+ |
attack_detected | Nárůst botů na vašem webu | Business+ |
reputation_changed | Reputace propojené osoby se přesunula do jiného pásma | Business+ |
transaction_state_changed | Změnil se výsledek transakce, kterou jste odeslali na POST /v2/transactions | Business+ |
login_risk | Posouzení přihlášení přes POST /v2/logins/assess skončilo výsledkem challenge nebo review | Všechny |
trusted_device_enrolled | Zařízení se stalo pro účet důvěryhodným, nebo byla jeho důvěra obnovena | Všechny |
trusted_device_revoked | Důvěra zařízení byla odvolána — přes API nebo z dashboardu | Všechny |
affiliate_threshold_crossed | Doporučení pro affiliate partnera se zhoršilo (none → review, none → hold, review → hold) | Všechny |
Názvy událostí používají podtržítka, nikdy tečky — žádné visitor.created ani
session.created neexistuje.
Webhook se přihlašuje ke konkrétním typům; samostatná hodnota * znamená „každý typ,
včetně těch přidaných později“. Výjimkou je pět typů: * nezahrnuje
account_assessment, login_risk, trusted_device_enrolled,
trusted_device_revoked a affiliate_threshold_crossed — každý z nich uveďte
výslovně. account_assessment hlásí pokrytí, nikoli nález, a proto přichází zhruba
jednou za návštěvu, která nese linked_id — objem zcela jiného řádu než u ostatních
událostí. Typy pro riziko přihlášení a affiliate mají smysl jen pro integraci, která
tato API volá. Zástupný znak nastavený dřív, než tyto události vznikly, není souhlasem
s jejich přijímáním.
Neznámý typ je při vytvoření nebo úpravě odběru odmítnut s kódem 400, takže vás
překlep nenechá s webhookem, který se mlčky nikdy nespustí.
identificationJedna návštěva vytvoří až tři doručení, která sdílejí stejné request_id:
primary — počáteční verdikt, při načtení stránky.late — obohacení zhruba o devět sekund později, jakmile dorazí pomalé kontroly.correction — korekce na základě chování (ukazatel, klávesnice, scrollování).Spojujte je podle request_id a rozlišujte podle phase. Pozdější fáze má
přednost: pokud primary řekl human a correction říká bot, správná je ta
druhá.
Nespoléhejte na pořadí doručení. Každá fáze se doručuje nezávisle a podle
vlastního plánu opakování — pokud primary skončil v opakování a late uspěl hned
napoprvé, dorazí vám v opačném pořadí. Přednost určujte podle pole phase, ne podle
času přijetí.
Tyto tři jsou jediné fáze události identification. Dorazí k vám ještě jedna další
hodnota: account_takeover a account_assessment nesou phase: "beacon", protože
obě vznikají z behaviorálního beaconu relace.
Všimněte si nesouladu, který tím vzniká, protože se týká idempotence. Produkční
doručení identification má event_id přesně ve tvaru <request_id>:<phase>, ale
ostatní doručení tento vzorec porušují. account_takeover má <request_id>:ato —
přípona je literál ato, ne hodnota pole phase — a account_assessment má
<request_id>:ato-state-<state>. Testovací doručení odeslané z dashboardu má
<request_id>:test, zatímco phase v jeho těle stále uvádí primary — a tělo podle
schématu 1 pole phase nemá vůbec, takže přípona je vidět jedině v hlavičce.
Používejte event_id přímo jako klíč idempotence a nikdy jej neskládejte znovu
z request_id a phase. Porovnávejte s hodnotami, které zpracováváte, a cokoli
jiného ignorujte, místo abyste doručení odmítli.
Některé události nemají návštěvu: attack_detected (úroveň workspace),
reputation_changed (úroveň osoby), transaction_state_changed (úroveň transakce),
login_risk, trusted_device_enrolled a trusted_device_revoked (úroveň účtu)
a affiliate_threshold_crossed (úroveň affiliate partnera). Nemají request_id,
visitor_id ani žádný z bloků browser, geo, bot či decision — ty klíče prostě
chybí, stejně jako hlavička X-Tracio-Request-Id. account_takeover
a account_assessment naopak vznikají z konkrétní návštěvy a nesou celé identifikační
tělo pro váš plán. Pokud všechny události zpracováváte v jednom handleru, zkontrolujte
event dřív, než sáhnete na pole návštěvy.
Nové webhooky používají verzi payloadu 3. Stávající webhooky na verzi 1 nebo 2 si ponechávají zvolenou verzi; obě starší schémata jsou zmrazená a budou odstraněna 2027-04-12. Pro migraci zvolte verzi 3 na kartě webhooku v dashboardu.
| Verze | Stav |
|---|---|
1 | Zmrazená; odstraněna 2027-04-12 |
2 | Zmrazená; odstraněna 2027-04-12 |
3 | Výchozí pro nové webhooky; klíče ve snake_case |
Verze 3 nese stejná pole a hodnoty jako verze 2, s klíči ve snake_case a s version: 3
na nejvyšší úrovni. Algoritmus podpisu a hlavičky se nemění
(X-Tracio-Payload-Version: 3). Klíče uvnitř vašeho vlastního slovníku fields jsou
vaše data a nepřejmenovávají se.
| 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", // model grafického adaptéru, normalizovaný; chybí, když není znám "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 provoz a surová síťová cesta vycházejí přes různé sítě }, "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}Nulové a prázdné hodnoty se vynechávají. Řetězcová a číselná pole s nulovou
hodnotou (například bot.type u člověka nebo bot.antidetect_score: 0) v JSON
chybí — nedělejte z nich ve svých schématech povinná pole a vnořené bloky čtěte
obezřetně.
bot.score a decision.risk_score jsou desetinná čísla na škále 0..100 s jednou
číslicí za desetinnou čárkou — přesně ta čísla, která pro tutéž návštěvu vykazují
dashboard a Data API. (V zamrzlém schématu v1 mají jiné jednotky: zlomek 0..1, resp.
0..255.)
bot.type je buď název rozpoznaného bota, ověřený AI agent (ai_agent:<vendor>), nebo
označení rodiny. Slovník hodnot najdete v části Typy botů
— interní názvy kontrol se nezveřejňují v žádném plánu. ai_agent:<vendor> znamená,
že požadavek prokázal původ v infrastruktuře daného dodavatele — platným podpisem Web
Bot Auth vytvořeným klíčem, který dodavatel zveřejňuje, nebo adresou z rozsahů IP,
které dodavatel zveřejňuje — a ne že se za něj jen vydává v user agentu. Požadavek,
který se vydává za AI agenta a který vyvracejí všechny důkazy zveřejněné dodavatelem,
se hlásí jako impersonation.
| Pole | Typ | Popis |
|---|---|---|
version | number | Verze schématu payloadu (3) |
event | string | Typ události |
event_id | string | Identifikátor doručení — klíč idempotence; jedinečný pro danou událost a sdílený všemi vašimi webhooky, které ji přijímají, stabilní napříč opakováními |
request_id | string | Identifikátor návštěvy (UUID), sdílený všemi jejími fázemi |
phase | string | primary, late, correction; account_takeover a account_assessment nesou beacon |
visitor_id | string | Stabilní identifikátor návštěvníka |
linked_id | string | Propojený identifikátor dodaný klientem |
tag | string | Vlastní tag dodaný klientem |
fields | object | Vlastní pole připojená k návštěvě při sběru. Jen to, co dorazilo s návštěvou — pole připojené později přes Data API se ve webhooku nikdy neobjeví, v žádné fázi ani opakování. Klíče i hodnoty jsou řetězce. Chybí, když návštěva žádná pole nenesla |
timestamp | string | Čas události (RFC 3339) |
url | string | URL stránky, kde byla událost zachycena |
ip | string | IP adresa klienta |
user_agent | string | Surový řetězec user-agent klienta |
browser.name / .version | string | Detekovaný prohlížeč |
os.name / .version | string | Detekovaný operační systém |
device | string | Třída zařízení (např. desktop, mobile) |
gpu | string | Model grafického adaptéru tak, jak jej hlásí prohlížeč (WebGL), normalizovaný na čitelný název (Intel Iris Xe Graphics, Apple M1 Pro); Software renderer pro softwarové rasterizéry; chybí, když není znám |
geo | object | Geolokace podle IP: country, city, lat, lon, timezone |
network | object | vpn, proxy, tor, datacenter (booleany) a connection_type |
network.proxy_detected | boolean | HTTP provoz návštěvy a její surové síťové cesty vycházejí přes různé sítě — před prohlížečem stojí proxy nebo VPN. Dvě adresy téhož poskytovatele (NAT operátora, druhý výstup téže VPN) se nepočítají |
bot.result | string | human, bot nebo uncertain |
bot.type | string | Název bota, ověřený AI agent nebo označení rodiny, když je bot detekován |
bot.score | number | Skóre bota (0–100) |
identification.confidence | number | Spolehlivost identifikace (0.0–1.0) |
identification.incognito | boolean | Anonymní režim prohlížení |
decision.action | string | real, fake nebo suspicious |
decision.risk_score | number | Souhrnné skóre rizika (0–100) |
extensions | array | Rozšíření prohlížeče detekovaná při této návštěvě jako stabilní strojové slugy (názvy, kategorie a odkazy do obchodů jsou v dashboardu a v Data API). Chybí, když nebylo nic detekováno |
Pro a vyšší — jak se návštěvník chová v čase:
{ "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 a vyšší — proč verdikt dopadl tak, jak dopadl:
{ "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, // veřejná adresa pozorovaná na surové síťové cestě, tedy adresa za proxy // nebo VPN; chybí, když žádná taková adresa pozorována nebyla "real_ip": { "address": "203.0.113.7", "country": "NL", "isp": "KPN" } }, "geo": { "isp": "Deutsche Telekom" }, // Desktopové prostředí ZMĚŘENÉ na linuxovém stroji ("Mint 22+", "Ubuntu", // "GNOME", "KDE"); User-Agent distribuci vyjádřit neumí. Chybí, když // nebylo určeno — u většiny linuxových návštěv a u každé nelinuxové. "os_environment": "Mint 22+", // Co návštěva o sobě tvrdila proti tomu, co naměřily nezávislé kontroly. // Přítomné jen tehdy, když bylo podvržení skutečně odhaleno; prázdné pole // `real` znamená "kontrola mlčela", nikdy "potvrzeno". U osy `gpu` nese // `claimed.gpu` deklarovaný adaptér pod stejným čitelným názvem modelu // jako pole `gpu` nejvyšší úrovně. "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"] } }, // Fakta o zařízení — co prohlížeč návštěvníka hlásí o stroji, očištěné na // naší straně. screen: rozlišení, barevná hloubka a device pixel ratio. // locale: vlastní preferované jazyky prohlížeče a jeho časové pásmo — na // rozdíl od geo.timezone, které se odvozuje z IP adresy; nesoulad mezi // nimi je běžnou známkou podvržené polohy. client_hints: User-Agent Client // Hints — architektura a bitovost procesoru, model zařízení (Android: kód // modelu v `model`, např. "SM-A556B", a jeho marketingový název ze seznamu // zařízení Google Play v `device_name`, např. "Samsung Galaxy A55 5G") a // přesná verze platformy; hlásí je jen prohlížeče založené na Chromiu. // Každý blok chybí, když návštěva taková data nepřinesla, proto s každým // počítejte jako s volitelným. "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" }, // Přítomné jen tehdy, když se grafický adaptér sám ohlásil jako virtuální; // hypervisor je uzavřený slovník (vmware, virtualbox, parallels, qemu, // hyperv, bochs, intel-gvt, vgpu). Chybějící blok znamená, že takové // doklady nejsou. "environment": { "virtual_machine": true, "hypervisor": "vmware" }, "decision": { "suspect_score": 4.1 }, "guidance": { "version": 3, "overall": "review" } // see below}Slovník kódů důvodů a význam severity najdete v části
Detekce botů.
Pole pro multi-accounting (Business a vyšší). Jakmile je pro váš workspace zapnutý
režim osob, tělo nese také person_id, blok reputation (score, categories,
overall_risk), linked_accounts_count a linked_visitors_count — tedy zda je
návštěvník propojen s dalšími návštěvníky a účty. Vrstva osob nyní běží v režimu
pozorování, a dokud nebude spuštěna naostro, tato pole v těle chybí. Nedělejte z nich
ve svých schématech povinná pole.
guidance nese hotová doporučení „co dělat“ pro každý integrační bod, takže nemusíte
odvozovat politiku ze surových skóre. Je přítomné v každé události v3 na plánu
Business+ — allow se uvádí výslovně:
{ "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 }}Každý scénář začíná na allow a po žebříčku se pohybuje jen nahoru:
allow → challenge → review → deny. Uvnitř scénáře vítězí nejpřísnější osa,
která se spustila, a overall je nejpřísnější napříč všemi čtyřmi scénáři.
| Doporučení | Platba | Registrace | Přihlášení | Affiliate |
|---|---|---|---|---|
allow | Zpracujte ji | Vytvořte ho | Pusťte je dovnitř | Připište konverzi |
challenge | 3-D Secure / potvrzení | Captcha, potvrzení e-mailem nebo telefonem | Step-up 2FA, opětovné ověření | Označte jako pochybnou, dokud se neprojeví aktivita |
review | Zpracujte, ale zařaďte ke kontrole | Vytvořte s omezeními | Pusťte je dovnitř a vyvolejte upozornění | Zadržte výplatu do prověření |
deny | Transakci nezpracovávejte | Vytvoření účtu odmítněte | Nepouštějte je dovnitř | Konverzi nepřipisujte |
basis uvádí osy, které doporučení určily — osa, jejíž návrh byl mírnější než
přijatý, se do seznamu nedostane. Čtěte jej podle kódu, místo abyste předpokládali
pevnou sadu:
Osa (basis) | Co znamená |
|---|---|
list | Návštěvník odpovídá jednomu z vašich seznamů workspace v režimu enforce. Seznam allow přebíjí všechny osy: každý scénář se stane allow a basis je přesně ["list"] |
rule | Návštěva odpovídá jednomu z vašich pravidel workspace v režimu enforce; pravidlo posouvá jen doporučení svého vlastního scénáře (pravidlo allow zmírní jen ten jeden) a přeskakuje se, když už zasáhl seznam |
bot | Detektor botů došel k závěru, že jde o automatizaci — zamítá všechny scénáře |
risk | Rozhodnutí a celkové skóre rizika návštěvy |
behavior | Behaviorální biometrie s dostatečnou spolehlivostí říká bot — přidává tření, nikoli odmítnutí |
account_takeover | Chování pod tímto účtem už neodpovídá profilu jeho vlastníka — přihlášení na challenge, platba na review |
network | Tor nebo VPN vycházející z datacentra |
affiliate_pattern | Tvar referralů generovaných sám sobě — affiliate konverze se drží jako challenge |
fraud_reputation, multiaccounting | Reputace a shlukování účtů osoby stojící za návštěvníkem; objeví se, jakmile bude vrstva osob spuštěna |
version je verze sady pravidel — zvyšuje se, jak se logika zlepšuje, proto si ji
uchovávejte v logech. Verze 2 zavedla osu list, první, která dokáže doporučení
zmírnit; verze 3 převedla prahy přihlášení u account_takeover na škálu skóre
verdiktu převzetí účtu, takže tatáž návštěva může dostat deny tam, kde verze 2 dala
challenge, a naopak.
Guidance je aditivní: nové scénáře přicházejí jako nové klíče, aniž by porušily
kontrakt. Pozdější fáze vítězí, s výjimkou částečného doporučení: doručení
spočítané na neúplné sadě vstupů je označeno "partial": true a částečné doporučení
nepřebíjí úplné doporučení přijaté dříve pro totéž request_id. V běžném doručení
pole partial zcela chybí.
Přesné prahy se záměrně nedokumentují. Doporučení, které lze zpětně rozklíčovat na
skóre, přestává být obranou. Existuje přesně jedna záměrná výjimka:
account_assessment.verdict.threshold, rozhodovací hranice verdiktu převzetí účtu
popsaná níže.
account_takeoverPouze Business a Enterprise; na nižších plánech se doručení nevytváří, ani když je
webhook k této události přihlášen. Tělo je celá identifikační obálka pro váš plán plus
blok account_alert a blok account_assessment popsaný v další části, doručeno
nejvýše jednou za návštěvu:
{ "version": 3, "event": "account_takeover", "event_id": "9c1f6a2e-3b7d-4c58-a1e2-6f0d8b4a7c31:ato", "request_id": "9c1f6a2e-3b7d-4c58-a1e2-6f0d8b4a7c31", "phase": "beacon", // upozornění vzniká z beaconu; jen event_id říká "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}Proč type nese verzi. Konstrukce za skóre se už dříve měnila: škála i základ
verdiktu. behavior-drift/2 je první verzovaná konstrukce. Pokud jste na tomto čísle
kalibrovali vlastní pravidla, berte změnu této hodnoty jako signál k jejich opětovné
kontrole.
account_alert.drift_score a identification.drift_score jsou totéž číslo
(od 2026-10-03): vyhlazené behaviorální skóre, na kterém byl verdikt učiněn, měřené
proti kruhu minulých důvěryhodných oken vlastníka účtu. Před tímto datem nesl
identification.drift_score surovou vzdálenost na jiné škále — nemíchejte hodnoty
přes toto datum. Když okno nebylo posouzeno, pole chybí, místo aby bylo nulové.
Použijte account_assessment.verdict, když potřebujete číslo spolu s prahem, se
kterým bylo porovnáno.
Ve v1 je blok accountAlert s type, linkedId a drift a blok posouzení chybí. Ve
v2 upozornění přejmenovává dvě pole — linkedId → accountId a drift →
driftScore — a přidává accountAssessment; v3 používá výše uvedené názvy ve
snake_case. Až budete přepínat verze, aktualizujte svůj handler, jinak vaše logika pro
převzetí účtu přestane data mlčky vidět.
account_assessmentPouze Business a Enterprise. Vyžaduje výslovný odběr: * ji nezahrnuje.
Bez této události znamená absence upozornění dvě různé věci zároveň: buď jsme relaci posoudili proti profilu vlastníka účtu a odpovídala, nebo jsme ji posoudit vůbec nemohli — profil vlastníka není připraven, okno bylo příliš řídké nebo provoz nepocházel od člověka. Obojí vypadá jako ticho a ticho se čte jako bezpečí. Obvykle jím není: za celý produkční den v září 2026 jsme se verdiktu zdrželi u 73 % oken a 80 % účtů. Proces postavený na „žádné upozornění znamená v pořádku“ stojí na stavu, který většinou nemáme.
{ "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 nikdy nechybí a nikdy není prázdný:
state | Význam | Co dělat |
|---|---|---|
assessed_clear | Posouzeno; chování odpovídá vlastníkovi | Nic |
assessed_alert | Posouzeno; chování se odchyluje | Step-up — upozornění account_takeover nese tentýž verdikt |
not_assessed | Verdikt nebyl učiněn | Nepovažujte za čisté; opřete se o vlastní signály |
reason (jen s not_assessed):
reason | Význam |
|---|---|
profile_not_ready | Referenční profil vlastníka nyní pro tento účet nedokáže vydat verdikt. Berte to jako stav, ne jako fázi: nemusí se nikdy změnit, takže nečekejte |
profile_learning | Referenční profil se stále shromažďuje |
window_too_sparse | Člověk tam byl, ale okno neslo příliš málo interakcí |
not_computable | Okno a referenční profil neměly žádné společné informativní osy |
bot_traffic | Dat bylo dost a nebyl tam žádný člověk k posouzení |
Od 2026-10-04 18:00 UTC se window_too_sparse a not_computable už nevydávají pro
vlastníka, jehož referenční profil je připraven — takové okno se hlásí jako
profile_not_ready. Oba důvody zůstávají v seznamu, protože je nesou dřívější
doručení: pokud na nich váš handler větví, směrujte profile_not_ready stejně
a neporovnávejte četnosti těchto důvodů přes toto datum.
state a reason nejsou omezeny plánem. verdict je Business+, stejně jako
drift_score.
Blok verdict je přítomen jen s assessed_clear a assessed_alert.
S not_assessed blok chybí, místo aby byl vyplněn nulami — score 0 vedle
threshold 0 by se četlo jako „chování přesně odpovídalo“.
| Pole | Význam |
|---|---|
severity | low nebo high — záměrně dvě pásma: prostřední pásmo by vyžadovalo druhý práh na téže škále |
score | Hodnota, na které byl verdikt učiněn |
threshold | Hodnota, se kterou bylo skutečně porovnáno |
scale | Jak se skóre sestavuje: manhattan-normalized je d/(1+d) v normalizovaném behaviorálním prostoru, kde 0 znamená přesnou shodu |
basis | ring — vzdálenost k nejbližšímu z minulých důvěryhodných oken vlastníka; centroid — vzdálenost k jeho zprůměrovanému profilu |
windows | Kolik oken bylo agregováno do skóre |
score a threshold jsou vždy na téže, pojmenované škále, takže dvojice reprodukuje
rozhodnutí: upozornění se spustí přesně tehdy, když score > threshold.
Jak často přichází. Nejvýše jedno doručení na stav a návštěvu — v praxi jedno na
návštěvu, která nese linked_id, ne jedno na behaviorální okno. Stav se může v rámci
jedné návštěvy změnit — například referenční profil dokončí učení a not_assessed se
změní na assessed_clear — a pak obdržíte obě doručení, v pořadí. Tento přechod je
možný, ne slíbený: profile_not_ready může přetrvat po celou dobu života účtu.
Co neslibuje. Nejlépe chráněné účty patří aktivním uživatelům s dlouhými relacemi a úplným referenčním profilem; nejhůře chráněné patří občasným uživatelům, pro které je převzetí nebezpečnější. Je to vlastnost konstrukce, ne práh, který lze vyladit. Událost hlásí behaviorální odchylku od referenčního profilu — netvrdí, že zachytí převzetí účtu obecně.
attack_detectedUpozornění na nárůst botů přichází ve společné obálce v3 s blokem attack. Nemá
identifikační pole — jde o událost na úrovni workspace:
{ "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 }}Webhooky na verzi payloadu 1 dostávají historické ploché schéma upozornění — to se nemění.
login_risk, trusted_device_enrolled a trusted_device_revokedTyto tři události vycházejí z vašich vlastních volání API rizika přihlášení
(POST /v2/logins/assess) a jeho důvěryhodných zařízení. Doručují se jen při
výslovném odběru podle názvu — * je nezahrnuje — a jen webhookům na verzi
payloadu 2 nebo 3; verze 1 pro ně tělo nemá.
login_risk se odesílá, když posouzení vrátí challenge nebo review — nikdy při
allow, deny nebo not_assessed. Jeho blok login_risk je uložené posouzení,
přesně tak, jak ho vrátila synchronní odpověď. Id události: login-risk:<id>, kde
<id> je náš identifikátor uloženého záznamu — vlastní assessment_id si vezměte
z těla.trusted_device_enrolled a trusted_device_revoked se odesílají při každém zapsání,
obnovení a odvolání důvěryhodného zařízení. Jejich blok trusted_device —
user_id, device_ref, enrolled_at, expires_at, revoked_at, revoked_by
(api nebo dashboard) — je snímek v okamžiku události; u
trusted_device_enrolled jsou revoked_at a revoked_by null. Id události:
trusted-device:<n>.Odesílají se asynchronně, zhruba do pěti sekund po volání, a nenesou hlavičku
X-Tracio-Request-Id — návštěvu najdete podle login_risk.request_id. Webhook je
notifikace pro audit a upozorňování; samotné rozhodnutí je synchronní odpověď.
{ "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_crossedDoručuje se jen při výslovném odběru podle názvu — * ji nezahrnuje — a jen
webhookům na verzi payloadu 2 nebo 3. Každých 15 minut TRACIO přepočítá skóre kvality
každého affiliate partnera s dostatkem dat a odešle událost, když se jeho doporučení
zhorší: none → review, none → hold nebo review → hold. Zlepšení nic neodesílá
a umožní, aby bylo další zhoršení znovu nahlášeno. Událost patří workspace: jde na
přihlášené webhooky všech prostředí a nenese hlavičku X-Tracio-Request-Id. Id
události: 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 jsou podíly affiliate partnera za posledních 90 dní, tytéž, které ukazuje
report v dashboardu: podíl registrací, které dospěly k prvnímu vkladu (reg_to_ftd),
podíl hráčů stále aktivních 14 dní po registraci (active14_share) a podíly konverzí
s duplicitním zařízením nebo osobou, z podvodného kruhu, od lovců bonusů, z datacentra,
VPN nebo od bota, označených jako chargeback nebo affiliate podvod a bez nalezené
návštěvy. TRACIO pouze doporučuje — samotné zadržení i jakékoli rozhodnutí o výplatě
zůstávají ve vaší affiliate platformě. Ve verzi payloadu 2 má totéž tělo klíče
v camelCase: blok je affiliateThreshold, s affiliateId, previousRecommendation,
qualityScore a tak dále.
transaction_state_changedBusiness a vyšší. Odesílá se, když se změní výsledek transakce, kterou jste odeslali na
POST /v2/transactions: pravidla rozhodla REVIEW nebo DECLINE (APPROVE nic
neodesílá), analytik poslal transakci ke kontrole v dashboardu, nebo byl štítek
nastaven, nahrazen či odstraněn. Použijte ji k zadržení bonusu, dokud je nárok
v prověřování, a k jeho odepření nebo uvolnění, jakmile je výsledek uzavřen, bez
dotazování. * tuto událost zahrnuje; doručuje se jen webhookům na verzi payloadu 2
nebo 3. Webhook vázaný na prostředí ji dostává jen pro toto prostředí.
{ "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 }}| Klíč | Význam |
|---|---|
effective_state | Výsledek, podle kterého jednat: stav od analytika nebo ze štítku, je-li nastaven, jinak state |
recommendations | Co udělat s bonusem, odvozeno z effective_state |
label, label_source, label_at | Aktuální štítek, kde byl nastaven (api nebo dashboard) a kdy; všechny tři jsou null, když transakce štítek nemá |
previous_effective_state | effective_state před touto změnou; null u události odeslané při automatickém rozhodnutí |
Blok ukazuje transakci tak, jak je v okamžiku odeslání události: pokud dvě změny
následují rychle po sobě, obě události nesou nejnovější stav, každá s vlastním
previous_effective_state a timestamp. Pořadí není zaručeno — řaďte podle
timestamp a aktuální stav transakce čtěte přes API. Id události:
txn-state:<id>:created pro automatické rozhodnutí a txn-state:<id>:<n> pro každou
pozdější změnu. Událost neříká, kdo štítek nastavil — jen odkud přišel.
reputation_changedBusiness a vyšší. Odesílá se, když se reputace osoby napříč zákazníky, kterou vaše
organizace viděla, přesune do jiného pásma (například clean → suspicious).
Vyžaduje vrstvu osob, která nyní běží v režimu pozorování — událost se proto spouští
jen pro workspacy, kde je zapnuté rozpoznávání identity napříč zařízeními. Jde
o událost na úrovni osoby: nemá request_id ani bloky návštěvy.
{ "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 }}Klíč bloku je reputation_change, ne reputation: ten druhý je obsazen reputací
v kontextu konkrétní návštěvy a jde o dvě různé věci. Schéma 1 doručuje historickou
plochou formu — tatáž pole na nejvyšší úrovni.
Každé doručení obsahuje hlavičku X-Tracio-Signature:
X-Tracio-Signature: t=1710432000,v1=5257a869e7ecebed…t je Unixový časový údaj (v sekundách), kdy byl požadavek podepsán.v1 je HMAC-SHA256 z "<t>.<rawRequestBody>" v hexadecimálním zápisu, klíčovaný
vaším tajným klíčem webhooku.Časový údaj je součástí podepisovaného obsahu, což poskytuje ochranu proti replay.
Dvě věci je třeba udělat správně, jinak ověřování v produkci selže:
v1=. Během rotace tajného klíče nese
hlavička dva podpisy a parser, který si nechá jen jeden z nich, bude po celé
okno rotace odmítat platná doručení.// 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")})Doručení podle schémat 2 a 3 navíc nesou X-Tracio-Signature-Ed25519
(t=<unix>,kid=<id>,v1=<base64>). HMAC tajný klíč znají obě strany, takže HMAC
dokazuje, že odesílatel klíč zná, ale ne že požadavek vznikl v TRACIO; to dokazuje
asymetrický podpis. Veřejné klíče jsou publikovány na
https://api.tracio.ai/.well-known/webhook-keys a indexovány podle kid: čtěte pole
public_key. Starší alias publicKey nese stejnou hodnotu a bude odstraněn
2027-04-12.
Testovací doručení odeslaná z dashboardu jsou podepsána pouze HMAC — soukromý klíč
platformy žije na doručovacích uzlech a záměrně není dashboardu dostupný. Ověřovač,
který Ed25519 tvrdě vyžaduje, musí testovací doručení propustit (nesou příponu
:test v event_id), jinak testování z dashboardu selhává, zatímco produkce je zdravá.
Stejná opatrnost platí pro kontroly formátu: testovací doručení nese request_id ve
tvaru test_<hex> a jako visitor_id literál test_visitor, takže handler, který je
validuje proti produkčním tvarům, odmítne doručení, které je jinak v pořádku.
Po rotaci zůstávají oba tajné klíče 24 hodin platné a hlavička nese oba podpisy, takže můžete aktualizovat konfiguraci, aniž byste přišli o doručení. Akce Revoke now toto okno zkrátí. Tajný klíč na své straně aktualizujte do 24 hodin: jakmile se okno zavře, starý klíč přestane odpovídat, a pokud váš endpoint odpoví na neplatný podpis kódem 4xx, pět takových odpovědí za sebou webhook vypne.
| Hlavička | Popis |
|---|---|
Content-Type | application/json |
X-Tracio-Signature | t=<unix>,v1=<hmac_sha256_hex> — dvě v1= během okna rotace |
X-Tracio-Signature-Ed25519 | Podpis platformy, t=<unix>,kid=<id>,v1=<base64> (pouze v2 a v3) |
X-Tracio-Event-Id | Identifikátor doručení — klíč idempotence |
X-Tracio-Request-Id | Identifikátor návštěvy, odpovídá request_id (pouze v2 a v3). Odesílá se jen u událostí návštěvy — identification, account_takeover a account_assessment |
X-Tracio-Event-Type | Typ události (pouze v2 a v3) |
X-Tracio-Delivery-Attempt | Číslo pokusu, počínaje 1 (pouze v2 a v3) |
X-Tracio-Payload-Version | 3 nebo 2 (pouze v2 a v3) |
X-Tracio-Webhook-Id | Identifikátor webhooku, který toto doručení vytvořil |
Doručení se mohou opakovat a opakování nese totéž X-Tracio-Event-Id. Deduplikujte
podle něj:
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")})Všimněte si, že event_id je jedinečné na událost, ne na webhook: pokud několik
webhooků ve workspace odebírá tutéž událost, každý dostane doručení se stejným
identifikátorem. U identification se skládá jako <request_id>:<phase>, a proto se
tři fáze jedné návštěvy deduplikují nezávisle, místo aby splynuly v jednu. Události bez
návštěvy mají vlastní tvary — viz jednotlivé události výše.
Odpovídejte kódem 2xx — je to jediná známka toho, že doručení bylo přijato.
| Odpověď | Co se stane |
|---|---|
2xx | Doručení dokončeno |
429 Too Many Requests | Nepočítá se jako selhání a nespotřebuje pokus; delší Retry-After je respektován |
408, 425, 5xx, přerušené spojení | Opakuje se s rostoucí pauzou |
410 Gone | Endpoint se považuje za odstraněný — webhook se okamžitě vypne |
Ostatní 4xx | Opakuje se, ale pět za sebou webhook vypne — 400/401/404 opakováním nevyléčíte |
Plán opakování: 5 s → 30 s → 2 min → 10 min → 30 min → 2 h → 6 h (8 pokusů). První opakování se vejdou do minuty, takže krátký restart vaší služby vás nestojí notifikaci. Každá pauza je náhodně rozprostřena mezi polovinou a plnou hodnotou, aby po výpadku opakování nevystřelila v jedné salvě.
Automatické vypnutí vyžaduje zároveň práh (20 selhání za sebou nebo 5 chyb konfigurace) a nejméně 15 minut nepřetržitých selhání — krátký restart integraci nezabije, ani když bylo ve frontě mnoho doručení. Mezera delší než 15 minut počítání restartuje. Dashboard ukazuje důvod včetně kódu odpovědi a textu chyby a tlačítko Re-enable, které čítače vynuluje.
| Plán | Webhooků na workspace |
|---|---|
| Free | Není k dispozici |
| Pro | 5 |
| Business | 20 |
| Enterprise | 100 |
Endpointy musí být https s veřejnou IP adresou — privátní a loopback adresy jsou
odmítnuty, a to i při přesměrování — a nejvýše dvě přesměrování do hloubky.
Následují se pouze přesměrování 307 a 308. 301, 302 a 303 instruují
klienta, aby přešel na GET a zahodil tělo, takže doručení je nenásleduje a pokus se
počítá jako neúspěšný. Pokud váš load balancer normalizuje URL (přidává www nebo
koncové lomítko), nasměrujte webhook rovnou na finální URL.
Webhooky se spravují v dashboardu. Dashboard pohání management API v rozsahu
workspace, obsluhované na hostu aplikace (například https://app.tracio.ai/api/v1), a
endpointy níže jsou právě ty, které volá. Všechny endpointy webhooků žijí pod
/workspaces/{wsId}.
Není to rozhraní pro komunikaci mezi servery. Management API přijímá pouze JWT vaší relace dashboardu, kontrolované proti vaší roli ve workspace (RBAC); secret key
tracio_sk_…je zde odmítnut. Protože tato relace žije v prohlížeči a spolu s ním vyprší, berte volání níže spíš jako popis toho, co dělá dashboard, než jako integraci k automatizaci. Pro programový přístup z vlastního backendu použijte read-only 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": [] }'Podpisový tajný klíč generuje TRACIO a vrací jej jednou při vytvoření (a při
rotaci) v poli signingSecret. Uložte jej bezpečně — je to klíč, kterým ověřujete
podpisy.
{ "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" }}Při dalších čteních je signingSecret maskovaný (null) — odhalí jej jen vytvoření
a rotace tajného klíče.
| Metoda | Cesta | Popis |
|---|---|---|
GET | /workspaces/{wsId}/webhooks | Seznam webhooků |
PATCH | /workspaces/{wsId}/webhooks/{webhookId} | Aktualizovat url / events / status |
DELETE | /workspaces/{wsId}/webhooks/{webhookId} | Smazat webhook |
POST | /workspaces/{wsId}/webhooks/{webhookId}/test | Odeslat podepsané testovací doručení |
POST | /workspaces/{wsId}/webhooks/{webhookId}/secret/rotate | Rotovat podpisový tajný klíč |
GET | /workspaces/{wsId}/webhooks/{webhookId}/deliveries | Seznam nedávných pokusů o doručení |
Vraťte 2xx co nejrychleji a payload zpracujte asynchronně, abyste se vyhnuli
timeoutům:
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) }}Použijte na webhooku akci Test (nebo POST .../webhooks/{webhookId}/test) a
odešlete podepsaný vzorový payload na svůj endpoint, abyste ověřili, že je dostupný a
že podpisy kontroluje správně.
Pro lokální vývoj zpřístupněte svůj server tunelem, například přes ngrok:
ngrok http 3000# Use the generated URL as your webhook endpoint