Webhooks liefern Identifikationsereignisse in Echtzeit an Ihren Server. Jedes Mal,
wenn ein Besucher identifiziert wird, sendet TRACIO eine HTTP-POST-Anfrage an Ihre
konfigurierte Webhook-URL. Der Anfragebody ist der Ereignis-Payload — es gibt
keinen umhüllenden Envelope.
Der Body ist ein flaches JSON-Dokument in camelCase. Es ist eine kuratierte
öffentliche Teilmenge des internen Ereignisses — ohne Fingerprint-Hashes oder
interne Erkennungsmarker.
{ "requestId": "1710432000_abc123def", "phase": "primary", "visitorId": "X7fh2Hg9LkMn3pQr", "linkedId": "user_12345", "tag": "login", "timestamp": "2024-03-12T16:00:00Z", "url": "https://your-app.com/login", "ip": "94.142.239.124", "userAgent": "Mozilla/5.0 …", "browser": { "name": "Chrome", "version": "120.0" }, "os": { "name": "macOS", "version": "14.3" }, "device": "desktop", "geo": { "country": "CZ", "city": "Prague", "lat": 50.05, "lon": 14.4, "timezone": "Europe/Prague", "isp": "Example ISP" }, "network": { "vpn": false, "proxy": false, "tor": false, "datacenter": false, "connectionType": "residential" }, "bot": { "result": "human", "type": "", "score": 0.02 }, "identification": { "confidence": 0.95, "incognito": false, "visitType": "returning" }, "decision": { "action": "allow", "riskScore": 4, "suspectScore": 0.08 }}| Feld | Typ | Beschreibung |
|---|---|---|
requestId | string | Eindeutige Ereignis-Kennung (zugleich Idempotenzschlüssel) |
phase | string | primary oder late — siehe unten |
visitorId | string | Stabiler Besucher-Identifikator |
linkedId | string | Vom Client bereitgestellter verknüpfter Identifikator |
tag | string | Vom Client bereitgestelltes benutzerdefiniertes Tag |
timestamp | string | Ereigniszeitpunkt (RFC 3339) |
url | string | Seiten-URL, an der das Ereignis erfasst wurde |
ip | string | Client-IP-Adresse |
userAgent | string | Roher Client-User-Agent-String |
browser.name / .version | string | Erkannter Browser |
os.name / .version | string | Erkanntes Betriebssystem |
device | string | Geräteklasse (z. B. desktop, mobile) |
geo | object | IP-Geolokalisierung: country, city, lat, lon, timezone, isp |
network | object | vpn, proxy, tor, datacenter (Booleans) und connectionType |
bot.result | string | human, bot oder uncertain |
bot.type | string | Freitext-Automatisierungslabel, wenn ein Bot erkannt wird |
bot.score | number | Bot-Wahrscheinlichkeitsscore |
identification.confidence | number | Modellkonfidenz (0.0–1.0) |
identification.incognito | boolean | Privater/Incognito-Surfkontext |
identification.visitType | string | Besuchsklassifizierung |
decision.action | string | Empfohlene Aktion |
decision.riskScore | number | Aggregierter Risikowert (0–100) |
decision.suspectScore | number | Feingranularer Verdachtsscore |
phase unterscheidet zwei Zustellungen, die sich dieselbe requestId teilen:
primary — sofort gesendet, wenn der Besucher identifiziert wird.late — ein nachgelagertes Anreicherungsereignis (ein verfeinertes Bot-Verdikt
und etwas später erfasste Verhaltenssignale).Korrelieren Sie die beiden über requestId und unterscheiden Sie sie über phase.
Jede Zustellung enthält einen X-Tracio-Signature-Header:
X-Tracio-Signature: t=1710432000,v1=5257a869e7ecebed...t ist der Unix-Zeitstempel (Sekunden), zu dem die Anfrage signiert wurde.v1 ist der hex-kodierte HMAC-SHA256 von "<t>.<rawRequestBody>", mit
Ihrem Webhook-Secret als Schlüssel.Der Zeitstempel ist Teil des signierten Inhalts, was Replay-Schutz bietet —
verifizieren Sie gegen den rohen Anfragebody-Buffer (verwenden Sie nicht das
geparste/neu serialisierte JSON, sonst stimmt die Signatur nicht überein). Bauen Sie
den signierten Inhalt als Bytes: das Präfix "<t>." konkateniert mit dem rohen
Body-Buffer, dann HMAC darüber bilden.
// Express.js exampleimport express from "express"import crypto from "crypto"
const app = express()
// Capture the raw body so we can verify the signature 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 { // Parse "t=...,v1=..." const parts = Object.fromEntries(header.split(",").map((kv) => kv.split("=") as [string, string])) const t = parts["t"] const v1 = parts["v1"] if (!t || !v1) return false
// Sign the raw bytes: "<t>." prefix + the raw request body buffer. const signed = Buffer.concat([Buffer.from(`${t}.`, "utf8"), rawBody]) const expected = crypto.createHmac("sha256", secret).update(signed).digest("hex")
const a = Buffer.from(v1, "hex") const b = Buffer.from(expected, "hex") return a.length === b.length && crypto.timingSafeEqual(a, b)}
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")})Sie sollten außerdem Zustellungen ablehnen, deren t zu weit von der aktuellen
Zeit entfernt ist (zum Beispiel mehr als fünf Minuten Abweichung), als zusätzlichen
Replay-Schutz.
| Header | Beschreibung |
|---|---|
Content-Type | application/json |
X-Tracio-Signature | t=<unix>,v1=<hmac_sha256_hex> über "<t>.<rawBody>" |
X-Tracio-Event-Id | Die requestId des Ereignisses — als Idempotenzschlüssel verwenden |
X-Tracio-Webhook-Id | Kennung des Webhooks, der diese Zustellung erzeugt hat |
Am einfachsten verwalten Sie Webhooks visuell im Dashboard. Sie können auch
programmatisch über die Workspace-scoped Management-API verwaltet werden — dieselbe
API, die das Dashboard verwendet. Sie wird auf dem Anwendungs-Host bereitgestellt (zum
Beispiel https://app.tracio.ai/api/v1) und mit Ihrem Dashboard-Session-JWT
(Clerk) authentifiziert; jede Anfrage wird zusätzlich gegen Ihre Workspace-Rolle
(RBAC) geprüft. Es gibt kein eigenständiges API-Secret. Alle Webhook-Endpunkte liegen
unter /workspaces/{workspaceId}.
curl -X POST https://app.tracio.ai/api/v1/workspaces/{workspaceId}/webhooks \ -H "Authorization: Bearer <clerk-session-jwt>" \ -H "Content-Type: application/json" \ -d '{ "url": "https://your-server.com/webhook/tracio", "events": [] }'Das Signing-Secret wird von TRACIO generiert und einmalig bei der Erstellung
(und beim Rotieren) unter signingSecret zurückgegeben. Speichern Sie es sicher — es
ist der Schlüssel, mit dem Sie Signaturen verifizieren.
{ "ok": true, "data": { "id": "wh_abc123", "workspaceEnvironmentId": "b201f2ba-…", "url": "https://your-server.com/webhook/tracio", "events": [], "signingSecret": "f3a9…<hex>", "status": "active", "successRate": 100, "createdAt": "2024-03-12T16:00:00Z" }}Bei nachfolgenden Lesevorgängen ist das signingSecret maskiert (null) — es wird
nur beim Erstellen und beim Secret-Rotieren offengelegt.
| Methode | Pfad | Beschreibung |
|---|---|---|
GET | /workspaces/{workspaceId}/webhooks | Webhooks auflisten |
PATCH | /workspaces/{workspaceId}/webhooks/{webhookId} | url / events / status aktualisieren |
DELETE | /workspaces/{workspaceId}/webhooks/{webhookId} | Einen Webhook löschen |
POST | /workspaces/{workspaceId}/webhooks/{webhookId}/test | Eine signierte Test-Zustellung senden |
POST | /workspaces/{workspaceId}/webhooks/{webhookId}/secret/rotate | Das Signing-Secret rotieren |
GET | /workspaces/{workspaceId}/webhooks/{webhookId}/deliveries | Letzte Zustellversuche auflisten |
GET | /workspaces/{workspaceId}/webhooks/{webhookId}/deliveries | Letzte Zustellversuche auflisten |
Zustellungen können erneut versucht werden, daher sollte Ihr Handler idempotent
sein. Deduplizieren Sie über den X-Tracio-Event-Id-Header (die requestId):
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")})Geben Sie so schnell wie möglich eine 2xx-Antwort zurück und verarbeiten Sie den
Payload asynchron, um Timeouts zu vermeiden. Nicht-2xx-Antworten (und
Verbindungsfehler) werden erneut versucht; wiederholte Fehlschläge können den Webhook
automatisch deaktivieren.
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) }}Verwenden Sie die Test-Aktion an einem Webhook (oder POST .../webhooks/:webhookId/test), um einen signierten Beispiel-Payload an Ihren
Endpunkt zu senden und zu bestätigen, dass er erreichbar ist und Signaturen korrekt
verifiziert.
Für die lokale Entwicklung stellen Sie Ihren Server über einen Tunnel wie ngrok bereit:
ngrok http 3000# Use the generated URL as your webhook endpoint