Webhook'lar, kimlik tespiti olaylarını gerçek zamanlı olarak sunucunuza iletir. Bir
ziyaretçi her tanımlandığında, TRACIO yapılandırdığınız webhook URL'sine bir HTTP
POST isteği gönderir. İstek gövdesi olayın yükünün kendisidir — sarmalayan bir
zarf yoktur.
Gövde, düz bir camelCase JSON belgesidir. İç olayın özenle seçilmiş, herkese açık
bir alt kümesidir — parmak izi hash'leri veya dahili tespit işaretleri içermez.
{ "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 }}| Alan | Tür | Açıklama |
|---|---|---|
requestId | string | Benzersiz olay tanımlayıcısı (aynı zamanda idempotency anahtarı) |
phase | string | primary veya late — aşağıya bakın |
visitorId | string | Kararlı ziyaretçi tanımlayıcısı |
linkedId | string | İstemci tarafından sağlanan bağlantılı tanımlayıcı |
tag | string | İstemci tarafından sağlanan özel etiket |
timestamp | string | Olay zamanı (RFC 3339) |
url | string | Olayın yakalandığı sayfa URL'si |
ip | string | İstemci IP adresi |
userAgent | string | Ham istemci user-agent dizesi |
browser.name / .version | string | Tespit edilen tarayıcı |
os.name / .version | string | Tespit edilen işletim sistemi |
device | string | Cihaz sınıfı (örn. desktop, mobile) |
geo | object | IP coğrafi konumu: country, city, lat, lon, timezone, isp |
network | object | vpn, proxy, tor, datacenter (boolean) ve connectionType |
bot.result | string | human, bot veya uncertain |
bot.type | string | Bir bot tespit edildiğinde serbest biçimli otomasyon etiketi |
bot.score | number | Bot olasılık puanı |
identification.confidence | number | Model güveni (0.0–1.0) |
identification.incognito | boolean | Gizli/incognito tarama bağlamı |
identification.visitType | string | Ziyaret sınıflandırması |
decision.action | string | Önerilen eylem |
decision.riskScore | number | Toplam risk puanı (0–100) |
decision.suspectScore | number | İnce ayrıntılı şüphe puanı |
phase, aynı requestId'yi paylaşan iki teslimatı ayırt eder:
primary — ziyaretçi tanımlandığı anda gönderilir.late — bir sonraki zenginleştirme olayı (rafine edilmiş bir bot verdikti ve
biraz sonra yakalanan davranışsal sinyaller).İkisini requestId ile ilişkilendirin ve phase ile birbirinden ayırın.
Her teslimat bir X-Tracio-Signature başlığı içerir:
X-Tracio-Signature: t=1710432000,v1=5257a869e7ecebed...t, isteğin imzalandığı Unix zaman damgasıdır (saniye).v1, webhook secret'ınızla anahtarlanan "<t>.<rawRequestBody>" ifadesinin
hex olarak kodlanmış HMAC-SHA256'sıdır.Zaman damgası imzalanan içeriğin bir parçasıdır ve bu tekrar (replay) koruması
sağlar — ham istek gövdesi Buffer'ına karşı doğrulama yapın (ayrıştırılmış/
yeniden serileştirilmiş JSON'u kullanmayın, aksi halde imza eşleşmez). İmzalanan
içeriği bayt olarak oluşturun: "<t>." ön ekini ham gövde arabelleğiyle birleştirin,
ardından bunun HMAC'ini alın.
// 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")})Ek bir tekrar (replay) koruması olarak, t'si geçerli zamandan çok uzak olan (örneğin
beş dakikadan fazla sapma gösteren) teslimatları da reddetmek isteyebilirsiniz.
| Başlık | Açıklama |
|---|---|
Content-Type | application/json |
X-Tracio-Signature | "<t>.<rawBody>" üzerinden t=<unix>,v1=<hmac_sha256_hex> |
X-Tracio-Event-Id | Olayın requestId'si — idempotency anahtarı olarak kullanın |
X-Tracio-Webhook-Id | Bu teslimatı üreten webhook'un tanımlayıcısı |
Webhook'ları yönetmenin en basit yolu görsel olarak dashboard üzerinden yapmaktır.
Ayrıca workspace kapsamlı yönetim API'si aracılığıyla programatik olarak da
yönetilebilirler — dashboard'un kullandığı API'nin aynısı. Uygulama ana bilgisayarında
(örneğin https://app.tracio.ai/api/v1) sunulur ve dashboard oturumunuzun (Clerk)
JWT'siyle kimlik doğrulaması yapılır; her istek ayrıca workspace rolünüze (RBAC) göre
de denetlenir. Bağımsız bir API secret'ı yoktur. Tüm webhook uç noktaları
/workspaces/{workspaceId} altında bulunur.
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": [] }'İmzalama secret'ı TRACIO tarafından oluşturulur ve oluşturma sırasında (ve
rotasyon sırasında) signingSecret altında bir kez döndürülür. Onu güvenli bir şekilde
saklayın — imzaları doğrulamak için kullandığınız anahtar odur.
{ "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" }}Sonraki okumalarda signingSecret maskelenir (null) — yalnızca oluşturma ve
secret-rotasyon sırasında açığa çıkarılır.
| Metot | Yol | Açıklama |
|---|---|---|
GET | /workspaces/{workspaceId}/webhooks | Webhook'ları listele |
PATCH | /workspaces/{workspaceId}/webhooks/{webhookId} | url / events / status güncelle |
DELETE | /workspaces/{workspaceId}/webhooks/{webhookId} | Bir webhook'u sil |
POST | /workspaces/{workspaceId}/webhooks/{webhookId}/test | İmzalı bir test teslimatı gönder |
POST | /workspaces/{workspaceId}/webhooks/{webhookId}/secret/rotate | İmzalama secret'ını rotasyona sok |
GET | /workspaces/{workspaceId}/webhooks/{webhookId}/deliveries | Son teslimat girişimlerini listele |
GET | /workspaces/{workspaceId}/webhooks/{webhookId}/deliveries | Son teslimat girişimlerini listele |
Teslimatlar yeniden denenebilir, bu nedenle işleyiciniz idempotent olmalıdır.
X-Tracio-Event-Id başlığı (requestId) üzerinden yinelenenleri kaldırın:
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")})Zaman aşımlarını önlemek için mümkün olduğunca hızlı bir 2xx yanıtı döndürün ve
yükü asenkron olarak işleyin. 2xx olmayan yanıtlar (ve bağlantı hataları) yeniden
denenir; tekrarlanan başarısızlıklar webhook'u otomatik olarak devre dışı bırakabilir.
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) }}Bir webhook'ta Test eylemini (veya POST .../webhooks/:webhookId/test)
kullanarak uç noktanıza imzalı bir örnek yük gönderin ve erişilebilir olduğunu ve
imzaları doğru şekilde doğruladığını teyit edin.
Yerel geliştirme için, sunucunuzu ngrok gibi bir tünelle dışa açın:
ngrok http 3000# Use the generated URL as your webhook endpoint