Cihaz riski
Giriş, kayıt, parola sıfırlama ve onay sayfalarınızdaki her tarayıcıya Fingerprint'in visitorId'si gibi bir ziyaretçi kimliği ve risk motorunun buna bakışı. Sayfa genel site anahtarınızla kimlik tespiti yapar ve { visitorId, requestId, confidence } alır; sunucunuz güvenilir sonucu requestId ile okur ve girişlere iletir. Ücretli bir eklentidir, siz açana kadar kapalıdır (panelde Device risk).
| Parça | Kural |
|---|---|
visitorId | tqv_…, bu tarayıcı yalnızca sizin çalışma alanınızda: başka bir Touchque müşterisi aynı tarayıcı için başka bir kimlik görür. Silinen site verisinden, tarayıcının özelliklerini gösterdiği gizli pencerelerden ve tarayıcı güncellemelerinden sonra da aynı kalır. |
confidence | Tarayıcının cihaza bağlı anahtarı imzaladıysa 0.99, saklanan kimliğiyle 0.95, özelliklerinin eşleşmesiyle en çok 0.9 (aynı filo bilgisayarlarında daha düşük), ilk görüşte 0.5–0.6. |
requestId | dre_…, tek bir kimlik tespiti: bir hesap için, 15 dakika. Başka bir hesap device_report_replayed alır. |
network | Kimlik tespitinin adresi, risk motorunun gördüğü gibi: ülke, bölge ve şehir (asla koordinat değil), operatör (ASN), ağın türü (isp, hosting, tor) ve tarayıcının saatinin adrese uyup uymadığı (timezoneMismatch: başka ülkede bir VPN ya da proxy). |
velocity | Bu ziyaretçinin sayıları: 24 saatte ve 30 günde hesaplar, 24 saatte kayıtlar ve ülkeler, 7 günde saklanan kimlik sıfırlamaları (risk motorunun), son bir saatte ve günde kimlik tespitleri. |
bot | "Bot mu?" sorusuna tek cevap: detected (tarayıcı sürüldüğünü söylüyor: webdriver, headless tarayıcı), suspected (imleç yazılım gibi hareket etti, sıfır boyutlu pencere, yazılım grafiği) ya da not_detected, nedenleriyle. Sinyallerin özeti: kararı yine risk motoru verir. |
| Site anahtarı ve origin'ler | Site anahtarı (tqpk_…) geneldir; Touchque yalnızca organizasyonunuzun doğrulanmış alan adlarındaki (Organizasyon → Alan adları) ve listelediğiniz origin'lerdeki sayfalara cevap verir. identify text/plain bir POST'tur, CORS ön isteği yoktur: Touchque API alan adınıza connect-src'de izin verin. |
Script etiketi (script.js; site anahtarınız ve sürümünün bütünlük özetiyle, Device risk → Settings; ilk adı olan …/device.js'i yükleyen sayfalar aynı özetle çalışmaya devam eder) sayfa yüklenince tarayıcıyı tanır ve requestId'yi her formdaki gizli touchque_request_id alanına koyar, sonradan eklenen formlar dahil; 10 dakikada bir ve her gönderimden sonra yenisini alır. Gönderimde requestId'nin ardına bir nokta ve imlecin ve parmakların nasıl hareket ettiğini ekler: tarayıcının hesapladığı 18 karakterlik sayılar (sıçrayan imleçteki basışlar ve süreleri, uzun çizgilerin ve kaydırmaların ne kadar düz olduğu, dokunuşların süresi); asla bir konum ya da yol değil. data-pointer="off" bunu kapatır. Sunucunuz bu alanı geldiği gibi deviceRisk.check'e verir. Tek sayfalık uygulamalar window.TouchqueDevice'ı okur (ready, value(), requestId, refresh()); 1.6.0 sürümündeki etiket window.TouchQueDevice adını kullanıyordu.
<script src="https://cdn.touchque.com/device/v2.0.0/script.js" data-site-key="tqpk_…" data-purpose="sign_in"
integrity="sha384-a69uLWQn3eccd/RGOZIKDLN16dOQIXvX2eGDlOUYpmMMq4QVyqiTcuoRgT17JGLG" crossorigin="anonymous" async></script>report: missing, unknown ya da expired) ve device_identification_missing yalnızca numara eşleştirmeli bir doğrulama ister, diğerleri gibi gölge modunda başlar.deviceRiskRequestId ile login.request, risk.check) ve hesaptan önceki sayfalarda (POST /device-risk/check). Her sinyal yalnızca daha fazlasını ister: numara eşleştirmeli onay ya da passkey, kayıtta sizin ek kontrolünüz. Hiçbiri tek başına engellemez, hiçbiri bir kontrolü atlatmaz ve sinyal olan bir giriş asla sessiz olmaz. Sinyaller gölge modunda başlar (raporlanır, enforced: false); yöneticinizin "Her zaman doğrula" işaretlediği ziyaretçi hemen sorar.| code | Anlam | İstediği |
|---|---|---|
device_tampered | Tarayıcı fonksiyonları kodla değiştirilmiş ya da özellikler birbiriyle çelişiyor (anti-detect tarayıcı, sahteleme eklentisi). | number_match |
device_virtual | Sanal makine, yazılımsal grafik, başsız ya da otomatik tarayıcı. | number_match |
device_geo_mismatch | Tarayıcının saati adresin saat diliminden 2 saat ya da daha fazla farklı (proxy, VPN, uzaktan erişim). | passkey |
device_many_accounts | Bir ziyaretçide 24 saatte 3, 30 günde 6 hesap (aynı modeldeki filo bilgisayarlarında sayılmaz). | number_match |
device_signup_burst | Bir ziyaretçiden 24 saatte 3 kayıt. | number_match |
device_id_resets | Saklanan kimlik 7 günde iki kez silinip aynı ziyaretçi olarak geri gelmiş (gizli pencereler sayılmaz). | number_match |
device_many_countries | Bir ziyaretçi için 24 saatte 3 ülkeden adres. | number_match |
device_wrong_site | Kimlik tespiti sizin olmayan bir sitede yapılmış (benzer alan adı ya da proxy sayfası). | passkey |
device_report_replayed | Başka bir hesap için kullanılan kimlik tespiti ya da iki kez gönderilen rapor. | passkey |
device_private_window | Gizli pencere: yalnızca bağlam, tek başına asla sormaz. | none |
device_step_up_by_admin | Yöneticiniz ziyaretçiyi "Her zaman doğrula" olarak işaretledi: hemen uygulanır. | number_match |
device_identification_missing | Sunucunuz kimlik tespiti göndermeyen ya da doğrulanamayan (bilinmeyen, süresi dolmuş, başka çalışma alanının) bir sayfayı kontrol etti: etiket kaldırılmış ya da engellenmiş. Reklam engelleyiciler dürüst kullanıcılarda da bunu yapar. | number_match |
device_pointer_synthetic | İmleç ya da parmak el gibi değil yazılım gibi hareket etti: her basış imlecin az önce sıçradığı yere düştü ve 10 ms'den kısa sürdü, 10 ms'den kısa dokunuşlar ya da cetvel gibi düz çizgiler ve kaydırmalar (Playwright, Puppeteer, Selenium, AI tarayıcı ajanları). Etiketin formla gönderdiği sayılardan; asla bir konum ya da yol değil. Hiç hareket olmaması (klavye, ekran okuyucu) asla sinyal değildir; Voice Control ve Switch Control da böyle hareket eder, bu yüzden yalnızca sorar. | number_match |
Sayfa kaba özellikleri okur (işletim sistemi, tarayıcı ailesi ve ana sürümü, saat dilimi, diller, ekran boyutu, çekirdek sayısı, bellek sınıfı, dokunma noktaları, ekran kartının adı), tarayıcının bir deneme metnini nasıl çizdiğinin, bir deneme sesini nasıl çaldığının, 25 yaygın yazı tipinden hangilerine sahip olduğunun, birkaç Math sonucunun ve WebGL sınırlarının kısa özetlerini, ayrıca bulguları (değiştirilmiş tarayıcı fonksiyonları, sanal makine, başsız tarayıcı, tespit edilebildiği yerde gizli pencere). Touchque ham özellik saklamaz: yalnızca sizin çalışma alanınıza ait bir anahtarla anahtarlanmış özetler, kaba etiketler, hesap, ülke ve adresin ağının anahtarlanmış özeti; sizin seçtiğiniz 30–90 gün boyunca; adresin kendisi en çok bir gün. Asla MAC adresi, seri numarası ya da müşteriler arası cihaz grafiği yok.
Reklam engelleyiciler ve tarayıcı gizlilik listeleri bilinen üçüncü taraf parmak izi adreslerine giden betikleri ve çağrıları dürüst ziyaretçiler için de düşürür (onlar da Identification missing alır, en fazla ek doğrulama). Büyük siteler etiketi ve identify çağrısını Fingerprint'in vekil (proxy) entegrasyonları gibi kendi alan adlarından sunar: GET /_tq/device/vX.Y.Z/script.js, https://cdn.touchque.com/device/vX.Y.Z/script.js adresine (aynı baytlar, bu yüzden aynı bütünlük özeti), POST /_tq/device-risk/identify ise https://api.touchque.com/device-risk/identify adresine gider. Etiket sizin alan adınızı gösterir ve Content-Security-Policy için yalnızca 'self' yeter:
<script src="https://www.example.com/_tq/device/v2.0.0/script.js" data-site-key="tqpk_…" data-purpose="sign_in" data-api="https://www.example.com/_tq"
integrity="sha384-a69uLWQn3eccd/RGOZIKDLN16dOQIXvX2eGDlOUYpmMMq4QVyqiTcuoRgT17JGLG" crossorigin="anonymous" async></script>Vekil sırrı: vekilinizin arkasında her ziyaretçi Touchque'ya vekilinizin adresinden gelir. Ziyaretçinin adresini X-Touchque-Client-IP, çalışma alanınızın vekil sırrını X-Touchque-Proxy-Secret olarak gönderin (tqps_…, sahip ve yöneticiler için Device risk → Settings; API anahtarıyla GET /device-risk/settings). Touchque o zaman ziyaretçinin adresini kaydeder ve puanlar, adres başına sınırı (dakikada 60 kimlik tespiti) vekilinize değil ona uygular. Sırrı API anahtarı gibi sunucuda tutun; asla sayfada ya da git'te değil. Yanlış sır 403 proxy_secret_invalid döner; site anahtarını değiştirmek sırrı da değiştirir. Sır olmadan da vekil çalışır, ama her ziyaretçi vekilin tek adresine sayılır: yalnızca küçük siteler için. Kendi çerezlerinizi yolda düşürün, Origin başlığını geldiği gibi iletin (Touchque onu alan adlarınız ve origin listenizle karşılaştırır) ve sunucunuzda ziyaretçinin adresini deviceRisk.check'e clientIp olarak verin.
location /_tq/device/ {
proxy_pass https://cdn.touchque.com/device/;
proxy_ssl_server_name on;
proxy_set_header Cookie "";
proxy_set_header Authorization "";
}
location = /_tq/device-risk/identify {
limit_except POST { deny all; }
proxy_pass https://api.touchque.com/device-risk/identify;
proxy_ssl_server_name on;
proxy_set_header Cookie "";
proxy_set_header Authorization "";
proxy_set_header X-Forwarded-For "";
proxy_set_header X-Touchque-Proxy-Secret "tqps_…"; # Device risk → Settings; keep this file out of git
proxy_set_header X-Touchque-Client-IP $remote_addr; # behind a load balancer: set_real_ip_from + real_ip_header first
}// Route: www.example.com/_tq/* Secret: wrangler secret put TOUCHQUE_PROXY_SECRET
export default {
async fetch(request, env) {
const url = new URL(request.url);
if (request.method === 'GET' && url.pathname.startsWith('/_tq/device/')) {
return fetch('https://cdn.touchque.com/device/' + url.pathname.slice('/_tq/device/'.length));
}
if (request.method === 'POST' && url.pathname === '/_tq/device-risk/identify') {
return fetch('https://api.touchque.com/device-risk/identify', {
method: 'POST',
headers: {
'Content-Type': 'text/plain',
Origin: request.headers.get('Origin') || '',
'X-Touchque-Proxy-Secret': env.TOUCHQUE_PROXY_SECRET,
'X-Touchque-Client-IP': request.headers.get('CF-Connecting-IP') || '',
},
body: await request.text(),
});
}
return new Response('Not found', { status: 404 });
},
};// middleware.ts (Next.js 16: proxy.ts, export function proxy). TOUCHQUE_PROXY_SECRET in the environment.
import { NextRequest, NextResponse } from 'next/server';
export const config = { matcher: '/_tq/:path*' };
export async function middleware(req: NextRequest) {
const { pathname } = req.nextUrl;
if (req.method === 'GET' && pathname.startsWith('/_tq/device/')) {
return NextResponse.rewrite(new URL('https://cdn.touchque.com/device/' + pathname.slice('/_tq/device/'.length)));
}
if (req.method === 'POST' && pathname === '/_tq/device-risk/identify') {
const r = await fetch('https://api.touchque.com/device-risk/identify', {
method: 'POST',
headers: {
'Content-Type': 'text/plain',
Origin: req.headers.get('origin') ?? '',
'X-Touchque-Proxy-Secret': ({}).TOUCHQUE_PROXY_SECRET ?? '',
// On Vercel the first x-forwarded-for entry is the visitor; behind your own load balancer, its header.
'X-Touchque-Client-IP': (req.headers.get('x-forwarded-for') ?? '').split(',')[0].trim(),
},
body: await req.text(),
});
return new Response(r.body, { status: r.status, headers: { 'Content-Type': 'application/json', 'Cache-Control': 'no-store' } });
}
return new Response('Not found', { status: 404 });
}{
"rewrites": [
{ "source": "/_tq/device/:path*", "destination": "https://cdn.touchque.com/device/:path*" },
{ "source": "/_tq/device-risk/identify", "destination": "https://api.touchque.com/device-risk/identify" }
]
}Sınırlar: bir ziyaretçi adresi dakikada 60 kimlik tespiti yapabilir; bir site anahtarı ve bir çalışma alanının deviceRisk.check çağrıları ayrı ayrı dakikada 1.200. Sınırın üstünde çağrı 429 ve Retry-After döner. Kendi sayınız Device risk → Settings'te görünür (GET /device-risk/settings içinde ratePerMinute); daha büyük bir site beklenen trafiğiyle desteğe (support@touchque.com) yazar, Touchque çalışma alanı için yükseltir.
Vercel yönlendirmeleri (rewrites) vekil sırrını ekleyemez: küçük siteler için kullanın, yoğun trafikte identify yolu için Next.js middleware'ini (Vercel'de de çalışır) seçin. Doğrulanmış bir Touchque Login özel alan adınız varsa vekile gerek yok: Touchque iki yolu da orada sunar ve Device risk → Settings etiketi data-api="https://login.example.com/_tq" ile gösterir.
Yoğun bir sitede milyonlarca kimlik tespiti olur, bu yüzden hiçbir şey hepsini listelemez: GET /device-risk/events (her SDK'da deviceRisk.searchEvents, panelde Identifications sekmesi) saklama süreniz içinde en çok 90 günlük (varsayılan 7) bir aralık için en yeniden başlayarak tek bir sayfa döner; sinyal, stepUp, sayfa (kind), origin, ip (bir adres ya da 203.0.113.0/24 gibi bir CIDR aralığı; asla geri dönmez), hesap, visitorId ya da requestId ile süzer, her biri bir indeks üzerinde. nextCursor null olana kadar onu cursor olarak geri verin (sayfa başına 1–100); from/to'yu kaydırarak sayfalamayın. Çok fazla okuyacak bir arama 422 search_too_broad döner: aralığı daraltın ya da bir filtre ekleyin. Hepsi birden için: bir veri talebi (Organizasyon → Veri talepleri, JSON ya da CSV, kimlik tespiti başına bir satır, adres yok), webhook'lar ve log akışı.
Device risk → Overview son 30 günü, 90 günü ya da 12 ayı gün gün (adım artırmalarıyla kimlik tespitleri, uygulanan kısmıyla sinyaller; kimlik tespitlerinden daha uzun, 400 gün saklanan günlük sayılardan) ve sayfa açıkken 10 saniyede bir yenilenen son kimlik tespitlerini gösterir. Uyarılar (Device risk → Settings → Alerts) bir şey olduğunda ekibinize haber verir; her kuralın açma/kapama düğmesi ve eşiği vardır, saatte en çok bir kez uyarır. Webhook'unuza ve (bir e-posta göndericisi kurulunca) organizasyonunuzun sahip ve yöneticilerine e-postayla gider. Uyarı bir girişi asla değiştirmez: onlara risk motoru karar verir.
| Uyarı | Ne zaman | Varsayılan |
|---|---|---|
signup_burst | Bir ziyaretçi 24 saatte bu kadar kayıt oldu | 5 |
many_accounts | Bir ziyaretçide 24 saatte bu kadar hesap | 5 |
tamper_spike | Bir saatte bu kadar değiştirilmiş ya da sanal tarayıcılı kimlik tespiti | 20 |
Webhook'unuz (Developers → Webhooks), bir kimlik tespiti ya da kontrol daha fazlasını isteyen bir sinyal verdiğinde DEVICE_RISK_SIGNAL (uygulanan ya da gölgede; aynı ziyaretçinin aynı sinyallerle sayfa yüklemeleri 10 dakikada bir, her kontrol), bir uyarı tetiklendiğinde DEVICE_RISK_ALERT alır. Diğer olaylar gibi imzalıdır. requestId, visitorId, sayfa, biliniyorsa hesap, güven, sinyaller ve vaka koduyla motorun kararını taşır; asla bir özellik değil. Log akışı olan bir SSO organizasyonunda (Splunk, Datadog, Sentinel, webhook, S3) aynı olaylar o akışa da gider, kategori device_risk.
| Endpoint | Kim |
|---|---|
POST /device-risk/identify | Sayfa (site anahtarı) |
GET /device-risk/events/{requestId} | Sunucunuz: güvenilir kimlik tespiti ve motorun kararı |
POST /device-risk/check | Sunucunuz: bir hesap için kimlik tespiti (kayıt, sıfırlama) |
login.request, risk.check | deviceRiskRequestId |
GET /device-risk/events | Sunucunuz ya da panel: kimlik tespitlerinde arama (filtreler, opak imleç) |
GET /device-risk/trend | Overview grafiği, 30, 90 ya da 365 gün |
GET /device-risk/recent | Son kimlik tespitleri (Overview'daki canlı akış) |
DEVICE_RISK_SIGNAL, DEVICE_RISK_ALERT | Sunucunuza webhook olayları |
// Sayfa: kayıt, giriş, parola sıfırlama ya da onay formu gönderilirken (script etiketi olmadan)
import { deviceRisk, REQUEST_ID_FIELD } from '@touchque/web';
const id = await deviceRisk.identify({ siteKey: 'tqpk_…', purpose: 'sign_up' }); // { visitorId, requestId, confidence }
form.elements[REQUEST_ID_FIELD].value = id?.requestId ?? ''; // gizli touchque_request_id alanı, etiketin doldurduğu alan
// Sunucunuz (Node): sayfanın kopyasına asla güvenmeyin; eksik ya da boş da bir cevaptır
const r = await tq.deviceRisk.check(req.body.touchque_request_id, { event: 'sign_up', externalUsername: email, clientIp: req.ip });
if (r.stepUp) askForEmailCode(); // daha fazlasını iste, asla reddetme
if (r.requestId) {
const event = await tq.deviceRisk.getEvent(r.requestId); // güvenilir kimlik tespiti
}
// Girişlerde check yerine: alanı login.request'e geldiği gibi verin
await tq.login.request({ externalUsername, type: 'LOGIN', clientIp: req.ip, deviceRiskRequestId: req.body.touchque_request_id });<!-- Giriş, kayıt, parola sıfırlama ve onay sayfalarınızda tek satır -->
<script src="https://cdn.touchque.com/device/v2.0.0/script.js" data-site-key="tqpk_…" data-purpose="sign_in"
integrity="sha384-a69uLWQn3eccd/RGOZIKDLN16dOQIXvX2eGDlOUYpmMMq4QVyqiTcuoRgT17JGLG" crossorigin="anonymous" async></script>
<!-- Her formda gizli touchque_request_id alanını doldurur; formun kendi amacı kazanır: -->
<form method="post" action="/signup" data-touchque-purpose="sign_up">…</form>
<!-- Sunucunuz (tek satır, alan geldiği gibi):
const r = await tq.deviceRisk.check(req.body.touchque_request_id, { event: 'sign_up', externalUsername: email });
if (r.stepUp) askForEmailCode(); -->field = request.form.get('touchque_request_id')
r = tq.device_risk.check(field, event='sign_up', external_username=email, client_ip=ip)
if r['stepUp']:
ask_for_email_code()
if r['requestId']:
event = tq.device_risk.get_event(r['requestId'])
# Girişlerde check yerine:
tq.login.request(user, client_ip=ip, device_risk_request_id=field)$field = $_POST['touchque_request_id'] ?? null;
$r = $tq->deviceRisk->check($field, 'sign_up', $email, $ip);
if ($r['stepUp']) { askForEmailCode(); }
if ($r['requestId']) { $event = $tq->deviceRisk->getEvent($r['requestId']); }
// Girişlerde check yerine:
$tq->login->request($user, clientIp: $ip, deviceRiskRequestId: $field);field := req.FormValue("touchque_request_id")
r, err := client.DeviceRisk.Check(ctx, touchque.DeviceRiskCheckOptions{RequestID: field, Event: "sign_up", ExternalUsername: email, ClientIP: ip})
if err == nil && r.StepUp { askForEmailCode() }
if err == nil && r.RequestID != "" {
event, _ := client.DeviceRisk.GetEvent(ctx, r.RequestID)
_ = event
}
// Girişlerde check yerine:
_, err = client.Login.RequestWithOptions(touchque.LoginRequestOptions{ExternalUsername: email, Type: "LOGIN", ClientIP: ip, DeviceRiskRequestID: field})string? field = Request.Form["touchque_request_id"];
var r = await tq.DeviceRisk.CheckAsync(field, new() { Event = "sign_up", ExternalUsername = email, ClientIp = ip });
if (r.StepUp) AskForEmailCode();
if (r.RequestId is { } requestId) { var ev = await tq.DeviceRisk.GetEventAsync(requestId); }
// Girişlerde check yerine:
await tq.Login.RequestAsync(new LoginRequestOptions { ExternalUsername = email, ClientIp = ip, DeviceRiskRequestId = field });String field = request.getParameter("touchque_request_id");
var r = tq.deviceRisk().check(DeviceRisk.CheckOptions.ofRequest(field)
.event("sign_up").externalUsername(email).clientIp(ip));
if (r.stepUp()) askForEmailCode();
if (r.requestId() != null) { JsonNode event = tq.deviceRisk().getEvent(r.requestId()); }
// Girişlerde check yerine:
tq.login().request(new LoginRequestOptions(email).clientIp(ip).deviceRiskRequestId(field));field = params[:touchque_request_id]
r = tq.device_risk.check(field, event: "sign_up", external_username: email, client_ip: ip)
ask_for_email_code if r["stepUp"]
event = tq.device_risk.get_event(r["requestId"]) if r["requestId"]
# Girişlerde check yerine:
tq.login.request(email, client_ip: ip, device_risk_request_id: field)# Sayfa (genel site anahtarı, kendi origin'inizden, text/plain)
POST /device-risk/identify
{ "siteKey": "tqpk_…", "report": "<deviceRisk.collect()>" }
→ { "visitorId": "tqv_…", "requestId": "dre_…", "confidence": 0.99 }
# Sunucunuz (imzalı istek)
GET /device-risk/events/dre_…
POST /device-risk/check { "requestId": "dre_…", "event": "sign_up", "externalUsername": "ayse@example.com", "clientIp": "88.230.12.4" }