A lightweight analytics tracking script with comprehensive browser fingerprinting and automatic event tracking.
- 🔍 Advanced Browser Fingerprinting - Canvas, WebGL, Audio Context, and hardware fingerprinting
- 📊 Automatic Event Tracking - Page views, clicks, visibility changes, and page unloads
- 🆔 Persistent Visitor Identification - Cookie-based visitor ID with 2-year expiry
- 🎯 UTM Parameter Tracking - Automatic capture of campaign parameters
- 🌐 Comprehensive Metadata - User agent, referrer, page URL, and host tracking
- 📍 Enhanced Click Tracking - Detects buttons, links, element text, and heatmap data (with screen dimensions)
- 🔌 JavaScript API - Custom event tracking and user identification
- 📱 Cross-Platform Support - Works on mobile and desktop browsers
- 🔐 GDPR Compliant - Built-in consent management, DNT support, and opt-out functionality
- ⏱️ Session Tracking - Automatic session management with 30-minute timeout
- 📦 Event Queue - Track events before script loads (like gtag.js)
- 🎨 Heatmap Ready - Click positions normalized by screen size
For optimal performance, initialize the queue before loading the script:
<!-- Initialize queue first -->
<script>
window.ZoriHQ = window.ZoriHQ || [];
</script>
<!-- Load script asynchronously -->
<script async src="https://cdn.zorihq.com/script.min.js"
data-key="your-publishable-key"></script>
<!-- Track events immediately (even before script loads) -->
<script>
window.ZoriHQ.push(['track', 'page_view']);
window.ZoriHQ.push(['identify', {
app_id: 'user_123',
email: 'user@example.com'
}]);
</script><script src="https://cdn.zorihq.com/script.min.js" data-key="your-publishable-key"></script>You can customize the ingestion endpoint and visibility tracking behavior:
<script
src="https://cdn.zorihq.com/script.min.js"
data-key="your-publishable-key"
data-base-url="https://your-custom-endpoint.com/ingest"
data-comeback-threshold="30000"
data-track-quick-switches="false">
</script>Configuration Options:
data-base-url- Custom ingestion endpoint (default:https://ingestion.zorihq.com/ingest)data-comeback-threshold- Minimum hidden duration (in milliseconds) to triggeruser_comebackevent (default:30000/ 30 seconds)data-track-quick-switches- Set to"true"to track all visibility changes like the old behavior (default:"false")
- Latest stable:
https://cdn.zorihq.com/script.min.js - Specific version:
https://cdn.zorihq.com/v1.0.6/script.min.js - Development/Latest:
https://cdn.zorihq.com/latest/script.min.js
Once loaded, the script exposes a global window.ZoriHQ object for custom tracking:
// Direct API (after script loads)
window.ZoriHQ.track('button_clicked', {
button_name: 'Sign Up',
page: 'homepage'
});
// Queue method (works before script loads)
window.ZoriHQ.push(['track', 'purchase_completed', {
product_id: 'prod_123',
amount: 99.99
}]);Link visitor cookies to your app users:
// Direct API
window.ZoriHQ.identify({
app_id: 'user_123', // Your app's user ID
email: 'user@example.com', // User email
fullname: 'John Doe', // Full name
plan: 'premium', // Additional properties
signup_date: '2025-01-15'
});
// Queue method
window.ZoriHQ.push(['identify', {
app_id: 'user_123',
email: 'user@example.com',
fullname: 'John Doe'
}]);// Grant consent
window.ZoriHQ.setConsent({
analytics: true, // Allow analytics
marketing: false // Deny marketing
});
// Check consent status
const hasConsent = window.ZoriHQ.hasConsent();
// Opt out completely (GDPR right to be forgotten)
window.ZoriHQ.optOut();
// Queue method
window.ZoriHQ.push(['setConsent', { analytics: true }]);
window.ZoriHQ.push(['optOut']);// Get visitor ID
const visitorId = await window.ZoriHQ.getVisitorId();
// Get current session ID
const sessionId = window.ZoriHQ.getSessionId();
// Queue method with callback
window.ZoriHQ.push(['getVisitorId', function(id) {
console.log('Visitor ID:', id);
}]);The script automatically tracks:
- page_view - On initial load with page title, path, search params, and hash
- click - Every click with enhanced element detection:
- Element type (button, link, input, or clickable)
- CSS selector (optimized, not 10 levels deep)
- Element text content
- Link destination (for links)
- Click coordinates (x, y)
- Screen dimensions (for heatmap normalization)
- Data attributes
- session_start - When a new session begins
- session_end - When session expires (includes duration and page count)
- user_comeback - When user returns after being away for significant time (>30 seconds by default)
- Includes
hidden_duration_msandhidden_duration_seconds - Reduces event bloat by ignoring quick tab switches
- Includes
- left_while_hidden - When user closes/navigates away while page was hidden
- Includes
hidden_duration_msandhidden_duration_seconds - Helps identify background tab closures vs. active exits
- Includes
All events include:
- Unique visitor ID (persisted for 2 years)
- Session ID (30-minute timeout)
- UTM parameters (if present in URL)
- Referrer
- User agent
- Page URL and host
- Timestamp (UTC)
Sessions automatically restart when:
- 30 minutes of inactivity pass
- User arrives with different UTM parameters (new campaign)
- Browser session ends
Session data includes:
- Session duration (milliseconds)
- Page count (pages viewed in session)
- Campaign attribution (preserved from session start)
On first visit, the script generates a comprehensive fingerprint including:
- Screen resolution, color depth, orientation
- Browser properties (user agent, platform, languages, timezone)
- Hardware info (CPU cores, memory, touch points)
- Canvas and WebGL fingerprints
- Audio context fingerprint
- Available media devices
- Network connection info
- Battery status (if available)
The fingerprint is stored in localStorage and used to help identify returning visitors even if cookies are cleared.
The script respects user privacy and includes built-in GDPR compliance:
<script>
window.ZoriHQ = window.ZoriHQ || [];
// Set consent before tracking starts
window.ZoriHQ.push(['setConsent', {
analytics: true, // Essential analytics
marketing: false // Optional marketing
}]);
</script>The script automatically respects the browser's Do Not Track header by default. If DNT is enabled, no tracking occurs.
Users can completely opt out and delete all their data:
window.ZoriHQ.optOut();
// Deletes: cookies, localStorage, blocks future tracking| Cookie | Purpose | Expiry | Required |
|---|---|---|---|
zori_visitor_id |
Anonymous visitor tracking | 2 years | Yes (with consent) |
zori_session_id |
Session tracking | Browser close | Yes (with consent) |
zori_consent |
Consent preferences | 2 years | Always |
- Cookies: Visitor ID, session ID, consent preferences
- localStorage: Browser fingerprint, session data, identified user info
- Server: Events, timestamps, page URLs, user agents
All data can be deleted via optOut() method.
- Node.js 23+
- pnpm 10+
pnpm install# Build minified version
pnpm run build
# Or run minification directly
pnpm run minifyThe build creates dist/script.min.js (~6.6KB minified).
This project uses semantic versioning with automated releases via GitHub Actions.
We follow the Conventional Commits specification:
<type>[optional scope]: <description>
[optional body]
[optional footer(s)]
- feat: A new feature (triggers minor release)
- fix: A bug fix (triggers patch release)
- perf: Performance improvements (triggers patch release)
- docs: Documentation changes (triggers patch release)
- refactor: Code refactoring (triggers patch release)
- test: Adding or updating tests (no release)
- build: Build system changes (no release)
- ci: CI/CD changes (no release)
- chore: Maintenance tasks (no release)
Add BREAKING CHANGE: in the footer or ! after type for major releases:
feat!: remove support for legacy browsers
BREAKING CHANGE: This version drops support for Internet Explorer
# Feature (minor release)
git commit -m "feat: add click heatmap tracking"
# Bug fix (patch release)
git commit -m "fix: resolve cookie domain issues"
# Performance improvement (patch release)
git commit -m "perf: optimize fingerprint generation"
# Breaking change (major release)
git commit -m "feat!: redesign tracking API"
# Documentation (patch release)
git commit -m "docs: update installation instructions"
# No release
git commit -m "test: add unit tests for cookie handling"
git commit -m "ci: update GitHub Actions workflow"
git commit -m "chore: update dependencies"- Development: Make changes and commit using conventional commit messages
- Push to main: Push directly or merge PR to
mainbranch - Automatic Release: When code is pushed to
main, semantic-release automatically:- Analyzes commits to determine version bump
- Generates changelog
- Updates version in package.json
- Creates git tag
- Creates GitHub release with build artifacts
- Automatic CDN Deployment: When a git tag is created, the deploy-cdn workflow automatically:
- Builds the minified script
- Deploys to Cloudflare R2 at three URLs (versioned, latest, root)
- Verifies deployment success
The build process creates:
dist/script.min.js- Minified version (~6.6KB)dist/script.obfuscated.js- Obfuscated version (~45KB) - only for special builds
The minified version is what gets deployed to the CDN.
The script is automatically deployed to Cloudflare R2 when a git tag (e.g., v1.0.6) is pushed or a GitHub release is published:
- Versioned URL:
https://cdn.zorihq.com/v{version}/script.min.js- Immutable, cache forever - Latest URL:
https://cdn.zorihq.com/latest/script.min.js- Always points to newest version - Root URL:
https://cdn.zorihq.com/script.min.js- Always points to newest version
To deploy a specific version manually:
gh workflow run deploy-cdn.yml --ref main -f version=v1.2.3Or via GitHub UI: Actions → Deploy to CDN → Run workflow → Enter version
For automated releases and CDN deployment, configure these secrets in your GitHub repository:
GH_PAT- Personal Access Token with repo permissions (optional, allows release to trigger deploy)GITHUB_TOKEN- Automatically provided by GitHub (fallback if PAT not set)
CLOUDFLARE_API_TOKEN- Cloudflare API token with R2 read/write permissionsCLOUDFLARE_ACCOUNT_ID- Your Cloudflare account IDCLOUDFLARE_R2_BUCKET_NAME- Your R2 bucket name (e.g.,zorihq-tracking-script)CLOUDFLARE_R2_CUSTOM_DOMAIN- Your custom domain (e.g.,cdn.zorihq.com)
To enable automatic CDN deployment when releases are created:
- Go to https://github.com/settings/tokens?type=beta
- Generate new fine-grained token
- Set repository access to your tracking script repo
- Grant permissions: Contents (read/write), Metadata (read), Pull requests (read/write)
- Add as
GH_PATsecret in your repository settings
Without this, you'll need to manually trigger CDN deployment or rely on the git tag trigger.
- Create R2 bucket in Cloudflare Dashboard
- Generate API token with R2 read/write permissions
- (Optional) Configure custom domain for your bucket
- Add all secrets to GitHub repository settings → Secrets and variables → Actions
MIT