harmony-web-tracker
v1.0.0
Published
Harmony JavaScript web analytics and event tracking library
Downloads
2
Readme
Harmony Web Events Tracker
A comprehensive JavaScript tracking library for web analytics and event tracking with extensive third-party integrations. This library provides unified analytics insights by tracking user interactions, page views, custom events, and integrating with various marketing and sales platforms.
🏗️ Architecture Overview
┌─────────────────────────────────────────────────────────────────┐
│ Website Page │
├─────────────────────────────────────────────────────────────────┤
│ ┌────────────────┐ │
│ │ harmony.js │ ← Main tracking script │
│ │ │ • Event collection & batching │
│ │ │ • User identification │
│ │ │ • Session management │
│ │ │ • Auto-loads integrations │
│ └────────┬───────┘ │
│ │ │
│ ├── Exposes Global APIs: │
│ │ • Harmony.identify() │
│ │ • Harmony.goal() │
│ │ • Harmony.addSharedProperty() │
│ │ │
│ ┌────────▼──────────────────────────────────┐ │
│ │ Auto-Loaded Integration Scripts │ │
│ ├────────────────────────────────────────────┤ │
│ │ Detected & loaded when conditions are met │ │
│ │ • Arcade • Drift • Pardot │ │
│ │ • HeyFlow • Marketo • Qualified │ │
│ │ • Calendly • Navattic • ZoomInfo │ │
│ └────────────────────────────────────────────┘ │
│ │
│ ┌────────────────────────────────────────────┐ │
│ │ Manual Integration Scripts │ │
│ ├────────────────────────────────────────────┤ │
│ │ Must be explicitly included on page │ │
│ │ • harmony-enrich.js → IP enrichment │ │
│ │ • harmony-6sense.js → Intent data │ │
│ │ • harmony-demandbase.js → ABM data │ │
│ │ • harmony-rollworks.js → Ad tracking │ │
│ │ • harmony-vector.js → Visitor ID │ │
│ └────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────┘
↓
Backend Server (Porter)
https://main-11072-06dd6b79-2kf5jph9.onporter.run🚀 Quick Start
Basic Setup
<script src="harmony.js"
data-apikey="YOUR_API_KEY"
data-server-url="https://main-11072-06dd6b79-2kf5jph9.onporter.run">
</script>With IP Enrichment
<script src="harmony.js" data-apikey="YOUR_API_KEY"></script>
<script src="harmony-enrich.js" data-apikey="YOUR_API_KEY"></script>With Manual Integrations
<script src="harmony.js" data-apikey="YOUR_API_KEY"></script>
<script src="harmony-6sense.js"></script>
<script src="harmony-demandbase.js"></script>
<script src="harmony-vector.js"></script>🔄 How Scripts Interact
1. Main Script (harmony.js)
The core tracking script that:
- Initializes on page load
- Creates global
Harmonyobject with public APIs - Tracks page views, clicks, scrolls, form submissions
- Auto-detects and dynamically loads integration scripts
- Batches and sends events to backend
- Manages user sessions and identification
2. Integration Loading Process
// harmony.js automatically detects integrations like this:
const integrations = [
{
name: 'drift',
check: () => !!document.querySelector('script[src*="js.driftt.com"]'),
script: 'harmony-drift.min.js'
},
{
name: 'marketo',
check: () => !!window.MktoForms2,
script: 'harmony-marketo-forms.min.js'
}
// ... more integrations
];
// Scans page periodically and loads matching integrations3. Data Flow Back to Main Script
Integration scripts extract data and feed it back:
// Example from harmony-drift.js
if (email) {
window.Harmony.identify(email);
}
// Example from harmony-6sense.js
window.Harmony.addSharedProperty({
key: 'company_id',
value: companyData.company_id,
properties: companyData
});
// Example from harmony-marketo.js
window.Harmony.goal('Marketo Form Submitted');📊 Data Flow Examples
Example 1: Visitor Identification via Drift Chat
1. Main script detects Drift script on page
2. Loads harmony-drift.js automatically
3. Drift integration monitors chat events
4. When visitor provides email in chat:
→ harmony-drift.js calls Harmony.identify(email)
→ Main script updates visitor identity
→ All future events include this identity
→ Sends updated data to backendExample 2: Company Enrichment via IP
1. harmony-enrich.js included on page
2. Makes POST request to /send-enrich endpoint
3. Server identifies company from visitor's IP address
4. Returns enrichment data (if available)
5. Script calls Harmony.addSharedProperty()
6. All future events include company dataExample 3: Intent Data from 6sense
1. harmony-6sense.js manually included on page
2. Checks localStorage for _6senseCompanyDetails
3. Extracts company and intent data
4. Calls Harmony.addSharedProperty() with:
- Company firmographics
- Buying stage
- Intent scores
5. Data attached to all visitor events🔌 Integration Capabilities
Auto-Loaded Integrations
These are automatically detected and loaded when their conditions are met:
| Integration | Detection Method | Purpose | Data Collected |
|------------|-----------------|---------|----------------|
| Arcade | iframe[src*="demo.arcade.software"] | Demo tracking | Demo progress, completion events |
| Calendly | URL parameter event_type_uuid | Meeting booking | Email, meeting details |
| Drift | script[src*="js.driftt.com"] | Live chat | Email, conversation events |
| Gravity Forms | WordPress forms | Form tracking | Form submissions, email |
| HeyFlow | script[src*="heyflow.app"] | Interactive content | Form data, engagement |
| Intercom | Intercom widget | Customer messaging | Visitor ID, email |
| Marketo | window.MktoForms2 | Marketing automation | Form submissions |
| Navattic | script[src*="js.navattic.com"] | Product demos | Demo interactions |
| Pardot | script[src*="pi.pardot.com"] | B2B marketing | Form data, email |
| Qualified | script[src*="qualified"] | Conversational marketing | Chat interactions |
| ZoomInfo Chat | script[src*="insent.ai"] | B2B chat | Chat events, lead data |
Manual Integrations
These must be explicitly included on your page:
| Integration | Script | Purpose | Data Collected |
|------------|--------|---------|----------------|
| 6sense | harmony-6sense.js | Intent data platform | Company info, buying stage, intent scores |
| Albacross | harmony-albacross.js | Lead generation | Company identification from IP |
| Demandbase | harmony-demandbase.js | ABM platform | Company firmographics |
| Enrich | harmony-enrich.js | IP enrichment | Company data from IP lookup |
| Leadoo | harmony-leadoo.js | Lead conversion | Form submissions |
| RollWorks | harmony-rollworks.js | ABM advertising | Account data, ad interactions |
| Vector | harmony-vector.js | Visitor tracking | Unique visitor ID |
🔧 Configuration Options
Script Tag Attributes
| Attribute | Required | Description | Example |
|-----------|----------|-------------|---------|
| data-apikey | ✅ | Your API key for authentication | data-apikey="abc123" |
| data-server-url | ❌ | Custom server endpoint | data-server-url="https://your-server.com" |
| data-fingerprint-apikey | ❌ | Enable Fingerprint.js v2 | data-fingerprint-apikey="fp_key" |
| data-cookieless | ❌ | Enable cookieless tracking | data-cookieless="true" |
| data-cross-domain | ❌ | Enable cross-domain tracking | data-cross-domain="true" |
| data-auto-identify | ❌ | Auto-identify from forms | data-auto-identify="true" |
| data-privacy-mode | ❌ | Hash emails with SHA1 | data-privacy-mode="true" |
| data-interval-start | ❌ | Initial send interval (ms) | data-interval-start="5000" |
| data-interval-increment | ❌ | Interval increment (ms) | data-interval-increment="2000" |
| data-only-identify | ❌ | Only track identification | data-only-identify="true" |
📡 API Reference
Harmony.identify(identity, properties)
Identifies the current visitor.
// Simple email identification
Harmony.identify('[email protected]');
// With additional properties
Harmony.identify('[email protected]', {
name: 'John Doe',
company: 'Acme Corp',
plan: 'enterprise'
});
// With object (for integrations)
Harmony.identify({
email: '[email protected]',
intercom_visitor_id: 'abc123'
});Harmony.goal(name, properties, date)
Tracks a custom goal/event.
// Simple goal
Harmony.goal('Clicked Pricing');
// With properties
Harmony.goal('Viewed Demo', {
demo_type: 'product_tour',
duration: 120
});
// With custom date
Harmony.goal('Scheduled Meeting', {
meeting_type: 'sales'
}, new Date('2025-09-10'));Harmony.addSharedProperty(config)
Adds properties shared across all events (typically company data).
Harmony.addSharedProperty({
key: 'company_id',
value: 'acme_corp_123',
properties: {
company_name: 'Acme Corporation',
company_domain: 'acme.com',
company_industry: 'Technology',
company_size: 500
}
});🔍 Event Types Tracked
Automatic Events
- enter-page: Page views with referrer, URL, title
- onclick: Click interactions with element details
- onsubmit: Form submissions
- onsearch: Search queries (on input[type="search"])
- scroll-depth: Scroll percentage (25%, 50%, 75%, 100%)
- identify: User identification events
- onerror: JavaScript errors (if enabled)
Custom Events
Use Harmony.goal() to track any custom event relevant to your business.
Event Structure
Each event contains:
{
actionLog: [{
action: {
actionType: "enter-page|onclick|onsubmit|scroll-depth|custom|identify",
actionDate: "2025-09-06T10:30:00.000Z",
actionElement: "button.cta-button", // for clicks
actionText: "Get Started", // for clicks
actionUrl: "https://...", // for clicks with links
actionNumber: 75, // for scroll depth
actionName: "Demo Completed", // for custom goals
actionProperties: {...} // for custom goals
},
url: "https://yoursite.com/page"
}],
customerObject: {
website: "yoursite.com",
apiKey: "YOUR_API_KEY",
version: "1.3.168",
// ... other metadata
},
userObject: {
uuid: "unique-visitor-id",
identity: "[email protected]", // if identified
// ... device/browser info
}
}📈 Event Volume Estimates
Typical Event Generation per Visitor Session
| Event Type | Average per Session | Notes | |------------|-------------------|--------| | Page Views | 3-5 events | One per page visited | | Clicks | 5-15 events | Depends on engagement | | Scroll Depth | 4-12 events | 4 per page (25%, 50%, 75%, 100%) | | Form Submissions | 0-2 events | Conversion dependent | | Custom Goals | 0-5 events | Business specific | | Total per Session | 12-39 events | Average ~20-25 events |
Daily Event Volume Projections
For capacity planning, here are typical daily event volumes:
| Site Size | Daily Visitors | Sessions/Day | Events/Day | Storage/Day | |-----------|---------------|--------------|------------|-------------| | Small Site | 100 | 150 | 3,000-6,000 | ~2-4 MB | | Medium Site | 1,000 | 1,500 | 30,000-60,000 | ~20-40 MB | | Large Site | 10,000 | 15,000 | 300,000-600,000 | ~200-400 MB | | Enterprise | 100,000 | 150,000 | 3M-6M | ~2-4 GB | | High Traffic | 1,000,000 | 1,500,000 | 30M-60M | ~20-40 GB |
Event Batching & Transmission
Events are batched to optimize network usage:
- Initial batch: Sent after 5 seconds (configurable)
- Subsequent batches: Every 2 seconds increment
- Session end: All remaining events sent on page hide/unload
- Batch size: Typically 5-20 events per request
- Request size: ~1-3 KB per batch
Factors Affecting Event Volume
Higher volumes expected with:
- Single Page Applications (more interactions, fewer page loads)
- E-commerce sites (product browsing, cart interactions)
- Content-heavy sites (more scroll events)
- Sites with chat widgets (additional integration events)
- B2B sites with multiple forms
Lower volumes expected with:
- Simple landing pages
- Blog/content sites with low interaction
- Sites with high bounce rates
- Mobile traffic (typically fewer interactions)
Storage Considerations
Per event storage:
- Raw event: ~300-500 bytes
- With enrichment: ~500-1000 bytes
- Compressed: ~150-300 bytes
Monthly storage estimates:
- Small site (3K events/day): ~100 MB/month
- Medium site (50K events/day): ~1.5 GB/month
- Large site (500K events/day): ~15 GB/month
- Enterprise (5M events/day): ~150 GB/month
🧪 Testing & Debugging
Debug Mode
Add hsdebug=true to URL to enable verbose console logging:
https://yoursite.com?hsdebug=trueStaging Mode
Add hsstaging=true to test staging version:
https://yoursite.com?hsstaging=trueTest Server
A test server is included for local development:
cd test-server
npm install
npm startVisit http://localhost:3000/dashboard to see tracked events.
🏗️ Development
Setup
npm installCommands
# Run ESLint
npm run lint
# Auto-fix linting issues
npm run lint:fix
# Start test server
cd test-server && npm startBuilding for Production
The library is distributed via NPM and served through JSDelivr CDN:
- Work in
developbranch - Bump version:
npm version patch - Publish to NPM:
npm publish - Push changes to git
- Purge CDN cache at https://www.jsdelivr.com/tools/purge
🔐 Privacy & Security
Data Collection
- No personal information collected by default
- IP addresses used for enrichment but not stored
- Fingerprinting available but optional
- GDPR/CCPA compliant options available
Privacy Mode
Enable with data-privacy-mode="true":
- All emails are hashed with SHA1
- No PII stored or transmitted in clear text
Cookieless Tracking
Enable with data-cookieless="true":
- Uses fingerprinting instead of cookies
- Maintains privacy while tracking visitors
📈 Performance Considerations
- Script size: ~9.11 KB (minified + gzipped)
- Batching: Events batched and sent at intervals
- Beacon API: Primary transport with XHR fallback
- Bot detection: Automatically filters bot traffic
- Iframe handling: Configurable iframe tracking
- Dynamic loading: Integrations loaded only when needed
🏛️ Backend Architecture
Current Implementation (MVP)
The current Harmony implementation uses a streamlined architecture optimized for simplicity and cost-effectiveness:
┌──────────────────────────────────────────────────────────────┐
│ CLIENT LAYER │
│ Browser → Harmony.js → Batched Events → Beacon/XHR │
└────────────────────────┬─────────────────────────────────────┘
│
┌────────────────────────▼─────────────────────────────────────┐
│ PORTER INGESTION SERVICE │
│ │
│ API Endpoints: │
│ ├── /send → Direct to GCS │
│ ├── /send-enrich → IP Enrichment + GCS │
│ └── /send-error → Error logging │
└────────────────────────┬─────────────────────────────────────┘
│
┌────────────────────────▼─────────────────────────────────────┐
│ GOOGLE CLOUD STORAGE │
│ │
│ Event Files (Partitioned by date/hour): │
│ └── /events/2025/09/06/14/{uuid}.json │
└────────────────────────┬─────────────────────────────────────┘
│
┌────────────────────────▼─────────────────────────────────────┐
│ BATCH PROCESSING │
│ │
│ BigQuery Scheduled Queries: │
│ ├── Hourly: Session aggregation │
│ ├── Daily: User journey analysis │
│ └── Weekly: Cohort analysis │
└───────────────────────────────────────────────────────────────┘This architecture handles:
- 100K-1M events/day comfortably
- < 500ms ingestion latency
- $10-50/month infrastructure cost
- Simple debugging and maintenance
Future Architecture (Scale Phase)
When you need real-time processing, multiple consumers, or > 10M events/day:
┌──────────────────────────────────────────────────────────────┐
│ CLIENT LAYER │
│ Browser → Harmony.js → Batched Events → Beacon/XHR │
└────────────────────────┬─────────────────────────────────────┘
│
┌────────────────────────▼─────────────────────────────────────┐
│ INGESTION LAYER │
│ │
│ Load Balancer → API Gateway │
│ ├── /send → Pub/Sub Topic │
│ ├── /send-fast → Priority Topic │
│ ├── /send-enrich → Enrichment Service │
│ └── /send-error → Error Topic │
└────────────────────────┬─────────────────────────────────────┘
│
┌────────────────────────▼─────────────────────────────────────┐
│ GOOGLE PUB/SUB │
│ │
│ Topics & Subscriptions: │
│ ├── events-main → Multiple subscribers │
│ ├── events-priority → Fast processing │
│ └── events-dlq → Failed message handling │
└────────────────────────┬─────────────────────────────────────┘
│
┌────────────────┴────────────────┐
▼ ▼
┌──────────────────┐ ┌──────────────────┐
│ STREAM PROCESS │ │ GCS WRITER │
│ │ │ │
│ • Real-time │ │ • Batch files │
│ • Enrichment │ │ • Compression │
│ • Alerts │ │ • Partitioning │
└──────────────────┘ └──────────────────┘
│ │
▼ ▼
┌──────────────────┐ ┌──────────────────┐
│ REAL-TIME DB │ │ COLD STORAGE │
│ (Firestore) │ │ (GCS) │
└──────────────────┘ └──────────────────┘When to Add Pub/Sub
You should consider adding Pub/Sub when:
- Traffic exceeds 1M events/day consistently
- You need real-time dashboards (< 1 second latency)
- Multiple systems need to consume the same events
- You're experiencing data loss due to processing delays
- You need guaranteed delivery with retry logic
- Different event types need different processing speeds
Signs you DON'T need Pub/Sub yet:
- Batch processing (hourly/daily) is sufficient
- Single consumer (just storing to GCS/BigQuery)
- < 100K events/day
- Cost optimization is priority
- Team is small (less complexity to manage)
Migration Path from Direct-to-GCS to Pub/Sub
The migration is straightforward when needed:
// Current Implementation (Direct to GCS)
async function handleEvent(event) {
const file = storage.bucket('events')
.file(`${date}/${uuid}.json`);
await file.save(JSON.stringify(event));
}
// Future Implementation (With Pub/Sub)
async function handleEvent(event) {
// Just publish to topic - minimal code change
await pubsub.topic('events').publish({
data: Buffer.from(JSON.stringify(event))
});
}
// Subscriber handles GCS writing (separate service)
subscription.on('message', async (message) => {
const event = JSON.parse(message.data.toString());
await writeToGCS(event);
message.ack();
});Migration steps:
- Deploy Pub/Sub subscriber that writes to GCS
- Update ingestion service to write to both GCS and Pub/Sub
- Verify data consistency
- Switch to Pub/Sub only
- Add more subscribers as needed
Architecture Benefits by Stage
| Stage | Architecture | Benefits | Limitations | |-------|-------------|----------|-------------| | MVP | Direct to GCS | Simple, cheap, reliable | No real-time, single consumer | | Growth | GCS + Cache | Fast recent data access | Still batch-oriented | | Scale | Pub/Sub + GCS | Real-time, multiple consumers | More complex, higher cost | | Enterprise | Full streaming | Sub-second latency, complex processing | Requires dedicated team |
Cost Comparison
Direct to GCS (Current):
- GCS Storage: ~$0.02/GB/month
- GCS Operations: ~$0.005/10K operations
- Total: $10-50/month for 1M events/day
With Pub/Sub (Future):
- Pub/Sub: ~$40/TB
- Additional GCS costs (same as above)
- Cloud Functions/Run for processing
- Total: $100-500/month for 1M events/day
The current architecture is perfectly adequate for most B2B SaaS companies. HockeyStack likely started with direct storage too and evolved as they scaled.
📝 License
This project is proprietary software. All rights reserved.
🆘 Support
For issues, questions, or feature requests, please contact the Harmony support team.
