@pulsora/core#

The core Pulsora package provides privacy-first analytics tracking for web browsers. It handles pageviews, custom events, and user identification without cookies or personal data collection.

When to Use Core Package#

Choose @pulsora/core when you:

  • Build with vanilla JavaScript - No framework required
  • Need maximum flexibility - Direct control over all tracking calls
  • Use non-React frameworks - Vue, Svelte, Angular, etc.
  • Want minimal bundle size - ~2KB with zero dependencies
  • Integrate server-side - Node.js, Deno, edge functions
  • Build browser extensions - Lightweight and framework-agnostic

Use @pulsora/react instead if you're building a React application and want hooks like usePageview() and useEvent() - see React package comparison.

Core vs React Package#

Feature @pulsora/core @pulsora/react
Framework Any (vanilla JS) React only
Bundle size 2.99KB gzipped 3.8KB gzipped
TypeScript ✅ Full support ✅ Full support
Tracking API Imperative Hooks + Imperative
Router integration Manual Automatic
Learning curve Simple Requires React knowledge

Example comparison:

// @pulsora/core (any framework)
import { Pulsora } from '@pulsora/core';
const pulsora = new Pulsora();
pulsora.init({ apiToken: 'pub_xxx' });
pulsora.pageview();
pulsora.event('click');

// @pulsora/react (React apps)
import { usePageview, useEvent } from '@pulsora/react';
function MyComponent() {
  usePageview(); // Automatic on mount
  const trackEvent = useEvent();
  return <button onClick={() => trackEvent('click')}>Click</button>;
}

Learn more about React package →

Features#

🔒 Privacy-First Design#

  • No cookies - Uses in-memory session storage
  • No localStorage - All data is ephemeral by default
  • Server-side visitor IDs - Backend generates anonymous identifiers
  • GDPR compliant - Privacy by design

⚡ Performance#

  • ~2KB gzipped - Minimal impact on bundle size
  • Async loading - Non-blocking script execution
  • SendBeacon API - Reliable data transmission
  • Automatic retries - Exponential backoff for failed requests

🎯 Automatic Tracking#

  • SPA support - Tracks route changes automatically
  • Pageview tracking - Zero configuration required
  • Referrer detection - Automatic source attribution
  • Session management - 30-minute inactivity timeout

🔧 Developer Experience#

  • TypeScript support - Full type definitions included
  • Framework agnostic - Works with any JavaScript framework
  • Extensible - Plugin system for custom functionality
  • Debug mode - Detailed console logging

How It Works#

1. Server-Side Visitor Identification#

When a visitor loads your site, Pulsora's backend generates an anonymous visitor identifier using:

fingerprint = SHA256(IP_Address + User_Agent + Website_ID + Salt)

This identifier is:

  • ✅ Generated entirely server-side
  • ✅ Anonymous (IP/UA never stored raw)
  • ✅ Temporary (expires with salt rotation)
  • ✅ GDPR compliant (pseudonymization)

The client receives this identifier and includes it with all tracking requests. No client-side fingerprinting techniques (Canvas, WebGL, Audio) are used.

Learn more about server-side fingerprinting →

2. Session Management#

Sessions are managed entirely in memory:

  • New session on first visit
  • 30-minute inactivity timeout
  • Persists across page navigation
  • Resets on browser close

3. Event Tracking#

All events include:

  • Visitor fingerprint
  • Session ID
  • Timestamp
  • Page URL
  • Referrer
  • Custom properties

Installation#

Choose your preferred installation method:

npm install @pulsora/core
import { Pulsora } from '@pulsora/core';

const pulsora = new Pulsora();
pulsora.init({ apiToken: 'your-token' });
<script
  async
  src="https://cdn.pulsora.co/v1/pulsora.min.js"
  data-token="your-token"
></script>

Basic Usage#

Track Pageviews#

Pageviews are tracked automatically by default, but you can also track them manually:

// Automatic (default behavior)
pulsora.init({
  apiToken: 'your-token',
  autoPageviews: true, // This is the default
});

// Manual tracking
pulsora.pageview();

// With custom properties
pulsora.pageview({
  title: 'Custom Page Title',
  url: '/custom-path',
  referrer: 'https://google.com',
});

Track Events#

Track any custom event with optional properties:

// Simple event
pulsora.event('video_play');

// Event with properties
pulsora.event('purchase', {
  product: 'Pro Plan',
  price: 99,
  currency: 'USD',
});

// Form submissions
pulsora.event('form_submit', {
  form_id: 'contact',
  fields: ['name', 'email', 'message'],
});

Advanced Usage#

Multiple Trackers#

Create separate trackers for different purposes:

// Main website tracker
const mainTracker = new Pulsora();
mainTracker.init({ apiToken: 'main-token' });

// Blog tracker
const blogTracker = new Pulsora();
blogTracker.init({ apiToken: 'blog-token' });

// Track to specific tracker
mainTracker.event('main_site_event');
blogTracker.event('blog_event');

Get Tracking Data#

Access fingerprint and session data for server-side attribution:

// Get visitor fingerprint (async)
const fingerprint = await pulsora.getVisitorFingerprint();

// Get session ID (sync)
const sessionId = pulsora.getSessionId();

// Use for payment attribution
fetch('/api/checkout', {
  method: 'POST',
  body: JSON.stringify({
    product_id: 'pro-plan',
    visitor_fingerprint: fingerprint,
    session_id: sessionId,
  }),
});

Custom Configuration#

pulsora.init({
  apiToken: 'your-token',
  endpoint: 'https://custom.endpoint.com/ingest', // Custom endpoint
  autoPageviews: false, // Disable automatic pageview tracking
  debug: true, // Enable debug logging
  maxRetries: 5, // Maximum retry attempts
  retryBackoff: 2000, // Initial retry delay in ms
});

Browser Compatibility#

Pulsora supports all modern browsers:

  • ✅ Chrome/Edge (last 2 versions)
  • ✅ Firefox (last 2 versions)
  • ✅ Safari (last 2 versions)
  • ✅ iOS Safari
  • ✅ Chrome for Android

Bundle Size#

The core package is optimized for minimal impact:

  • Minified: 8.5KB
  • Gzipped: 2.99KB
  • Brotli: 2.7KB

Security Considerations#

API Token Security#

  • Use public tokens only (never secret tokens in browser)
  • Tokens are scoped to specific domains
  • Rate limiting prevents abuse

Data Privacy#

  • No personal data is collected
  • All data is anonymous by default
  • User consent not required (no cookies)
  • Right to deletion supported

Real-World Use Cases#

SaaS Product Analytics#

// Track feature usage in your dashboard
const pulsora = new Pulsora();
pulsora.init({ apiToken: 'pub_xxx' });

// Track page visits
pulsora.pageview();

// Track feature interactions
document.querySelector('#export-btn').addEventListener('click', () => {
  pulsora.event('feature_used', {
    feature: 'data_export',
    format: 'csv',
  });
});

// Track login events
function handleLogin(user) {
  pulsora.event('user_login', {
    plan: user.subscription_plan,
  });
}

E-commerce Product Tracking#

// Track product views
function trackProductView(product) {
  pulsora.event('product_view', {
    product_id: product.id,
    category: product.category,
    price: product.price,
  });
}

// Track add to cart
function addToCart(product) {
  pulsora.event('add_to_cart', {
    product_id: product.id,
    quantity: 1,
    value: product.price,
  });
}

// Send fingerprint with checkout for attribution
async function checkout(cart) {
  const fingerprint = await pulsora.getVisitorFingerprint();

  await fetch('/api/checkout', {
    method: 'POST',
    body: JSON.stringify({
      items: cart.items,
      visitor_fingerprint: fingerprint,
      session_id: pulsora.getSessionId(),
    }),
  });
}

Vue.js Application#

// plugins/pulsora.js
import { Pulsora } from '@pulsora/core';

export default {
  install(app, options) {
    const pulsora = new Pulsora();
    pulsora.init({ apiToken: options.apiToken });

    // Make available globally
    app.config.globalProperties.$pulsora = pulsora;
    app.provide('pulsora', pulsora);

    // Track route changes
    if (options.router) {
      options.router.afterEach((to, from) => {
        pulsora.pageview({
          url: to.fullPath,
          title: to.meta.title || document.title,
          referrer: from.fullPath,
        });
      });
    }
  },
};

// main.js
import { createApp } from 'vue';
import router from './router';
import pulsoraPlugin from './plugins/pulsora';

const app = createApp(App);
app.use(pulsoraPlugin, {
  apiToken: 'pub_xxx',
  router,
});

Browser Extension#

// background.js
import { Pulsora } from '@pulsora/core';

const pulsora = new Pulsora();
pulsora.init({
  apiToken: 'pub_xxx',
  endpoint: 'https://api.pulsora.co/ingest',
});

// Track extension installation
chrome.runtime.onInstalled.addListener((details) => {
  if (details.reason === 'install') {
    pulsora.event('extension_installed');
  } else if (details.reason === 'update') {
    pulsora.event('extension_updated', {
      previous_version: details.previousVersion,
    });
  }
});

// Track feature usage
chrome.action.onClicked.addListener(() => {
  pulsora.event('toolbar_icon_clicked');
});

Troubleshooting#

Tracker Not Initializing#

Symptoms:

  • pulsora.event() doesn't send data
  • No network requests to pulsora.co
  • Console shows "Pulsora not initialized"

Solutions:

  1. Ensure init() is called before tracking:
// ❌ Wrong - tracking before init
const pulsora = new Pulsora();
pulsora.event('click'); // Won't work!

pulsora.init({ apiToken: 'pub_xxx' });

// ✅ Correct - init first, then track
const pulsora = new Pulsora();
pulsora.init({ apiToken: 'pub_xxx' });
pulsora.event('click'); // Works!
  1. Wait for async init to complete:
const pulsora = new Pulsora();
await pulsora.init({ apiToken: 'pub_xxx' });
// Now ready to track

Duplicate Pageviews#

Cause: Auto-pageview tracking enabled + manual pageview calls

Solution:

// Option 1: Disable auto-tracking
pulsora.init({
  apiToken: 'pub_xxx',
  autoPageviews: false, // Manual control
});

// Then track manually
pulsora.pageview();

// Option 2: Keep auto-tracking, don't call pageview() manually
pulsora.init({
  apiToken: 'pub_xxx',
  autoPageviews: true, // Let Pulsora handle it
});
// No manual pageview() calls needed

Events Not Showing in Dashboard#

Debugging checklist:

  1. Enable debug mode:
pulsora.init({
  apiToken: 'pub_xxx',
  debug: true, // See console logs
});

// Console will show:
// [Pulsora] Initialized with token: pub_xxx
// [Pulsora] Event tracked: button_click
// [Pulsora] Request sent: 200 OK
  1. Check API token format:
// ❌ Wrong - using secret token
apiToken: 'sec_xxx'; // Don't use in browser!

// ✅ Correct - using public token
apiToken: 'pub_xxx';
  1. Verify domain whitelist:
  • Go to Dashboard → Settings → Websites
  • Add your domain (e.g., example.com)
  • Include subdomains if needed (e.g., *.example.com)
  1. Check network requests:
// Open DevTools → Network tab
// Filter by "pulsora" or "ingest"
// Look for POST requests with status 200

// If you see 403 errors:
// - Check API token is correct
// - Verify domain is whitelisted

// If you see 429 errors:
// - Rate limit exceeded
// - Wait a few minutes or contact support

TypeScript Type Errors#

Error: Property 'init' does not exist on type 'Pulsora'

Solution:

// Ensure you're importing from the correct package
import { Pulsora } from '@pulsora/core'; // ✅ Correct
import { Pulsora } from 'pulsora'; // ❌ Wrong package name

// If using CDN with TypeScript:
// Create types/pulsora.d.ts
import type { PulsoraCore } from '@pulsora/core';

declare global {
  interface Window {
    pulsora: PulsoraCore;
  }
}

export {};

getVisitorFingerprint() Returns Undefined#

Cause: Fingerprint generation is async

Solution:

// ❌ Wrong - fingerprint not ready yet
const fingerprint = pulsora.getVisitorFingerprint();
console.log(fingerprint); // undefined

// ✅ Correct - await the promise
const fingerprint = await pulsora.getVisitorFingerprint();
console.log(fingerprint); // "abc123def456..."

// Or use .then()
pulsora.getVisitorFingerprint().then((fingerprint) => {
  console.log(fingerprint);
});

Next Steps#