Skip to content

Browser SDK

Lightweight client-side tracking SDK for sending user events, identifying users, and tracking page views. Published as @pyrx/synapse-browser on npm. Approximately 5 KB minified.


Installation

Drop the snippet into your HTML <head> to start tracking immediately.

html
<script>
!function(){var s=window.synapse=window.synapse||function(){
(s.q=s.q||[]).push(arguments)};
var e=document.createElement("script");
e.type="text/javascript";e.async=true;
e.src="https://storage.googleapis.com/cep-mvp-sdk/sdk/v1/synapse.min.js";
var x=document.getElementsByTagName("script")[0];
x.parentNode.insertBefore(e,x);
}();
 
synapse('init', { apiKey: 'psk_live_YOUR_API_KEY' });
synapse('page');
</script>

Commands called before the SDK script loads are queued and replayed automatically.

npm (For Bundlers)

bash
npm install @pyrx/synapse-browser
typescript
import { synapse } from '@pyrx/synapse-browser';
 
synapse('init', { apiKey: 'psk_live_YOUR_API_KEY' });
Warning

The Browser SDK sends the API key in request headers. Always use a browser-scoped API key for client-side code. Browser keys can only track events -- they cannot send emails, read contacts, or access admin endpoints. Create one in Dashboard > Settings > API Keys under the "Client-side" section.

Never use a data, full, or management scoped key in client-side code.


Initialization

javascript
synapse('init', {
apiKey: 'psk_live_YOUR_API_KEY', // Required
endpoint: 'https://synapse-events.pyrx.tech', // Default
flushInterval: 5000, // ms between flushes (default: 5000)
flushSize: 10, // events before auto-flush (default: 10)
debug: false, // Console logging (default: false)
});
ParameterTypeDefaultDescription
apiKeystringrequiredYour Synapse API key (browser scope recommended for client-side)
endpointstringhttps://synapse-events.pyrx.techEvent ingestion endpoint
flushIntervalnumber5000Milliseconds between automatic queue flushes
flushSizenumber10Number of queued events that trigger an immediate flush
debugbooleanfalseEnable [Synapse] console logging

Identify Users

Call after login to associate events with a known user.

javascript
synapse('identify', 'user-123', {
email: '[email protected]',
first_name: 'Jane',
last_name: 'Doe',
phone: '+1234567890',
});

The identify call:

  • Persists the user ID in localStorage so subsequent track calls include it automatically
  • Sends a $identify event with the provided traits
  • Sets contact_overrides with email, first_name, last_name, and phone fields (when provided) to upsert the contact server-side

Track Events

javascript
synapse('track', 'cart.abandoned', {
product: 'Widget',
price: 29.99,
currency: 'USD',
});
 
synapse('track', 'feature.used', {
feature: 'export_csv',
rows: 1500,
});

Events are queued in memory and flushed in batches to POST /v1/events/batch.


Page Views

javascript
// Automatically captures URL, path, title, and referrer
synapse('page');
 
// With custom properties
synapse('page', { section: 'pricing' });

Page views are tracked as $pageview events with automatic capture of url, path, title, and referrer.


Reset (On Logout)

javascript
synapse('reset');

Clears the identified user, generates a new anonymous ID, and removes the stored user ID from localStorage. Call this when a user logs out to prevent events from being attributed to the wrong user.


How It Works

  1. Events are queued in memory and persisted to localStorage
  2. The queue flushes every 5 seconds or when 10 events accumulate (configurable)
  3. Events are sent as a batch to POST /v1/events/batch
  4. On page unload, sendBeacon ensures queued events are delivered
  5. Failed flushes retry with exponential backoff (1s, 2s, 4s, up to 3 retries)
  6. The queue survives page refreshes via localStorage (max 500 events)

Partial-Accept Handling (SDK 0.5.0+)

From version 0.5.0, the SDK sends the X-Synapse-SDK-Version: 2 header and reads the batch endpoint's partial-accept response. It reconciles the local queue per rejected event:

  • invalid_schema events are dropped permanently. A malformed event can never be accepted, so re-queuing it would stall the queue forever. The SDK logs a console.warn naming the event's index and detail so you can fix your instrumentation.
  • over_limit events (and any unrecognized future reason) are re-queued and retried later with the existing exponential backoff — a workspace at its quota backs off rather than dropping data.
  • Accepted events are cleared from the queue as before.

A 422 response (every event was invalid_schema) drops those events instead of re-queuing the whole batch. Any other non-2xx status (403 over-quota, 429, 5xx) re-queues the whole batch for a later attempt.

Anonymous Tracking

Before identify is called, events are associated with an auto-generated anonymous ID stored in localStorage. After identify, the user ID replaces the anonymous ID. The anonymous ID is always included as _anonymous_id in event attributes for server-side identity stitching.

Data Flow

Browser SDK
-> queue in memory + localStorage
-> batch POST /v1/events/batch (every 5s or 10 events)
-> Synapse API -> event processing pipeline
-> triggers flows, updates contacts, BigQuery streaming

Full Example

A complete integration for a SaaS application.

html
<!DOCTYPE html>
<html>
<head>
<script>
!function(){var s=window.synapse=window.synapse||function(){
(s.q=s.q||[]).push(arguments)};
var e=document.createElement("script");
e.type="text/javascript";e.async=true;
e.src="https://storage.googleapis.com/cep-mvp-sdk/sdk/v1/synapse.min.js";
var x=document.getElementsByTagName("script")[0];
x.parentNode.insertBefore(e,x);
}();
 
synapse('init', { apiKey: 'psk_live_YOUR_API_KEY' });
synapse('page');
</script>
</head>
<body>
<script>
// After user logs in
function onLogin(user) {
synapse('identify', user.id, {
email: user.email,
first_name: user.firstName,
last_name: user.lastName,
});
synapse('track', 'user.logged_in', {
method: 'email',
});
}
 
// Track feature usage
function onFeatureUsed(featureName) {
synapse('track', 'feature.used', {
feature: featureName,
timestamp: new Date().toISOString(),
});
}
 
// On logout
function onLogout() {
synapse('track', 'user.logged_out');
synapse('reset');
}
</script>
</body>
</html>

React / Next.js Integration

Tracking Hook

tsx
// hooks/useSynapse.ts
import { useEffect } from 'react';
 
export function useSynapseIdentify(user: { id: string; email: string; name: string } | null) {
useEffect(() => {
if (!user || typeof window === 'undefined') return;
const synapseFn = (window as any).synapse;
if (synapseFn) {
synapseFn('identify', user.id, {
email: user.email,
first_name: user.name.split(' ')[0],
});
}
}, [user]);
}
 
export function trackEvent(name: string, props?: Record<string, unknown>) {
if (typeof window !== 'undefined') {
const synapseFn = (window as any).synapse;
if (synapseFn) synapseFn('track', name, props);
}
}

Next.js Script Loading

tsx
// app/layout.tsx
import Script from 'next/script';
 
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<html>
<head>
<Script id="synapse-loader" strategy="afterInteractive" src="/scripts/synapse-init.js" />
</head>
<body>{children}</body>
</html>
);
}

Create /public/scripts/synapse-init.js with your init snippet:

javascript
// public/scripts/synapse-init.js
!function(){var s=window.synapse=window.synapse||function(){
(s.q=s.q||[]).push(arguments)};
var e=document.createElement("script");
e.type="text/javascript";e.async=true;
e.src="https://storage.googleapis.com/cep-mvp-sdk/sdk/v1/synapse.min.js";
var x=document.getElementsByTagName("script")[0];
x.parentNode.insertBefore(e,x);
}();
 
synapse('init', { apiKey: 'YOUR_API_KEY' });
synapse('page');

TypeScript Types

The SDK exports TypeScript types for configuration and events.

typescript
import type { SynapseConfig, SynapseEvent } from '@pyrx/synapse-browser';
 
interface SynapseConfig {
apiKey: string;
endpoint?: string;
flushInterval?: number;
flushSize?: number;
debug?: boolean;
}
 
interface SynapseEvent {
event_name: string;
user_id?: string;
attributes: Record<string, unknown>;
occurred_at: string;
idempotency_key: string;
source: "sdk";
contact_overrides?: Record<string, string>;
}

Limits and Constraints

ConstraintValue
Max queue size500 events
Max batch size per flush50 events
Max retries on failure3
Default flush interval5 seconds
Default flush threshold10 events

Method Reference

CommandArgumentsDescription
synapse('init', config)SynapseConfigInitialize the SDK with your API key
synapse('identify', userId, traits)string, object?Associate events with a known user
synapse('track', eventName, props)string, object?Track a custom event
synapse('page', props)object?Track a page view with automatic URL capture
synapse('reset')noneClear user identity and generate new anonymous ID