Browser Fingerprinting
The tracker script generates a stable browser fingerprint to identify returning visitors even without cookies.
Signals Used
The fingerprint is a SHA-256 hash of these combined signals:
| Signal | Source |
|---|---|
| User Agent | navigator.userAgent |
| Language | navigator.language |
| Platform | navigator.platform |
| CPU Cores | navigator.hardwareConcurrency |
| Device Memory | navigator.deviceMemory |
| Screen Resolution | screen.width × screen.height |
| Color Depth | screen.colorDepth |
| Pixel Ratio | window.devicePixelRatio |
| Timezone Offset | Date().getTimezoneOffset() |
| Canvas | Rendered text/shapes → toDataURL() |
| WebGL | Unmasked vendor + renderer strings |
| Audio | OfflineAudioContext oscillator output sum |
Persistence
- Fingerprint is cached in
localStorageunder_pg_fpto avoid recalculation - Visitor ID (derived from fingerprint) is cached under
_pg_vid - Private browsing mode is handled gracefully (fingerprint regenerated each session)
Identity Derivation
On the server side, identityService.ts derives stable IDs:
- visitor_id =
SHA-256("visitor:" + fingerprint_id)— same across all sessions - device_id =
SHA-256("device:" + user_agent + "|" + fingerprint_id)— unique per browser+device
Identity Network
Each session start records the fingerprint → IP → ASN mapping in the identity_network D1 table. This builds a graph over time showing which fingerprints have been seen from which networks — useful for fraud detection and VPN identification.
D1 Record
The fingerprints table tracks per-fingerprint aggregates:
| Column | Description |
|---|---|
first_seen | Timestamp of first observation |
last_seen | Timestamp of most recent session |
total_sessions | Cumulative session count |
total_ips | Distinct IPs observed |
total_asns | Distinct ASNs observed |
vpn_suspected | VPN suspicion score |