Skip to main content
The Events SDK provides PostHog-style event capture with automatic session context, person identification, and batched transport. Events are sent to /ingest/e (single) and /ingest/batch (batch) endpoints.

Overview

The Composite SDK events system allows you to:
  • Capture custom events with arbitrary properties
  • Identify users and merge anonymous activity
  • Set person properties for user profiles
  • Filter events with beforeSend hooks
  • Automatic batching for efficient transport

Quick Start

Events are enabled by default. Start capturing immediately after initialization:
import composite from '@composite-inc/composite-js';

await composite.init({
  apiKey: 'pk_your_api_key',
  sessionRecording: true,
});

// Capture a custom event
composite.capture('button_clicked', { button_id: 'signup' });

Configuration Options

OptionTypeDefaultDescription
captureEventsbooleantrueEnable/disable event capture
beforeSendBeforeSendFn | BeforeSendFn[]undefinedHook(s) to modify or reject events
processPersonProfilebooleantrueWhether to process person profiles

Disabling Event Capture

Use session recording only without sending events:
await composite.init({
  apiKey: 'pk_your_api_key',
  sessionRecording: true,
  captureEvents: false,  // No events sent to /ingest/e
});
When captureEvents: false:
  • capture() calls are ignored
  • setPersonProperties() / setPersonPropertiesOnce() are ignored
  • createAlias() is ignored
  • identify() still updates local identity but doesn’t send an event
  • Session recording continues normally

Capturing Events

Basic Event Capture

// Simple event
composite.capture('button_clicked', { button_id: 'signup' });

// Event with more properties
composite.capture('purchase_completed', {
  product_id: 'sku_123',
  amount: 99.99,
  currency: 'USD',
});

Event with Person Properties

Set person properties along with the event using the third argument:
composite.capture('subscription_started',
  { plan: 'premium', price: 29.99 },
  {
    $set: {
      plan: 'premium',
      subscription_status: 'active'
    },
    $set_once: {
      first_subscription_date: new Date().toISOString(),
      acquisition_source: 'google_ads'
    }
  }
);

Capture Options

OptionTypeDescription
$setRecord<string, unknown>Person properties to set (overwrites existing)
$set_onceRecord<string, unknown>Person properties to set only if not already set
timestampstringOverride event timestamp (ISO 8601)
sys_uuidstringOverride auto-generated idempotency key
_urlstringPer-event endpoint override

Return Value

capture() returns the processed event or undefined if rejected:
const event = composite.capture('test_event', { foo: 'bar' });
if (event) {
  console.log('Event captured:', event.sys_uuid);
}
Events return undefined when rejected by beforeSend or when captureEvents is disabled.

Person Identification

Identifying Users

When a user signs up or logs in, identify them to merge their anonymous activity:
// User logs in
await composite.identify('user_123', {
  email: 'user@example.com',
  name: 'John Doe'
});
This sends a sys_identify event that:
  1. Links the anonymous ID to the identified user
  2. Sets the provided properties on the person profile
  3. Enables attribution of past anonymous events

Identify with Options

await composite.identify('user_123',
  { email: 'user@example.com' },
  {
    $set_once: {
      signup_date: new Date().toISOString(),
      referrer: document.referrer
    }
  }
);

Getting Identity Info

const distinctId = composite.getDistinctId();    // Current user ID
const anonymousId = composite.getAnonymousId();  // Anonymous ID
const sessionId = composite.getSessionId();      // Current session ID

Reset Identity (Logout)

await composite.reset();
Calling reset() generates a new anonymous ID, clears the identified user, and resets the session. Use this when users log out.

Person Properties

Set Properties (Overwrite)

Always overwrites existing values:
composite.setPersonProperties({
  email: 'user@example.com',
  plan: 'premium',
  company: 'Acme Corp'
});

Set Properties Once (First-Touch)

Only sets if the property doesn’t already exist:
composite.setPersonPropertiesOnce({
  first_seen: new Date().toISOString(),
  initial_referrer: document.referrer,
  signup_source: 'google_ads'
});

Create Alias

Link another identifier to the current person:
composite.createAlias('stripe:cus_abc123');
composite.createAlias('email:user@example.com');

beforeSend Hook

Modify or reject events before they’re sent.

Single Hook

await composite.init({
  apiKey: 'pk_your_api_key',
  beforeSend: (event) => {
    // Reject sensitive events
    if (event.event === 'sensitive_action') {
      return null;
    }

    // Modify event
    event.properties = {
      ...event.properties,
      sdk_version: '1.0.0'
    };

    return event;
  }
});

Multiple Hooks (Chained)

Hooks are executed in order. Return null from any hook to reject the event:
await composite.init({
  apiKey: 'pk_your_api_key',
  beforeSend: [
    // First hook: Add metadata
    (event) => {
      event.properties = { ...event.properties, env: 'production' };
      return event;
    },
    // Second hook: Filter events
    (event) => {
      if (event.event.startsWith('debug_')) {
        return null;  // Reject debug events
      }
      return event;
    },
    // Third hook: Sanitize
    (event) => {
      delete event.properties?.password;
      return event;
    }
  ]
});

Hook Signature

type BeforeSendFn = (event: CompositeEvent) => CompositeEvent | null;
  • Return the event (modified or unchanged) to send it
  • Return null to reject the event

Event Schema

CompositeEvent Structure

Events sent to the backend follow this schema:
interface CompositeEvent {
  /** Event name */
  event: string;
  /** User identifier */
  distinct_id: string;
  /** Idempotency key (auto-generated) */
  sys_uuid: string;
  /** ISO 8601 timestamp */
  timestamp: string;
  /** Event properties */
  properties: {
    sys_session_id: string;
    sys_window_id: string;
    sys_url: string;
    sys_referrer?: string;
    sys_host: string;
    sys_pathname: string;
    token: string;
    // ... your custom properties
  };
  /** Person properties to set */
  sys_set?: Record<string, unknown>;
  /** Person properties to set once */
  sys_set_once?: Record<string, unknown>;
  /** Anonymous ID for merge (sys_identify only) */
  sys_anon_distinct_id?: string;
}

Special Event Types

EventDescription
sys_identifyUser identification with merge
sys_setExplicit person property update
sys_create_aliasLink identifier to person

Complete Example

import composite from '@composite-inc/composite-js';

// Initialize with all options
await composite.init({
  apiKey: 'pk_your_api_key',
  sessionRecording: true,
  captureEvents: true,
  beforeSend: (event) => {
    // Strip PII from properties
    if (event.properties?.email) {
      event.properties.email = '[REDACTED]';
    }
    return event;
  }
});

// Track anonymous user browsing
composite.capture('page_viewed', { page: '/pricing' });
composite.setPersonPropertiesOnce({ first_page: '/pricing' });

// User signs up
await composite.identify('user_123', {
  email: 'user@example.com',
  name: 'John Doe'
});

// Track identified user activity
composite.capture('subscription_started',
  { plan: 'premium' },
  { $set: { plan: 'premium' } }
);

// Link external identifier
composite.createAlias('stripe:cus_abc123');

// User logs out
await composite.reset();

API Reference

Methods

MethodDescription
capture(event, properties?, options?)Capture a custom event
identify(distinctId, properties?, options?)Identify user and merge anonymous data
setPersonProperties(properties)Set person properties (overwrite)
setPersonPropertiesOnce(properties)Set person properties (first-touch)
createAlias(alias)Link identifier to current person
reset()Reset identity and session
getDistinctId()Get current user ID
getAnonymousId()Get anonymous ID
getSessionId()Get current session ID

Types

import type {
  CompositeEvent,
  CaptureOptions,
  BeforeSendFn,
} from '@composite-inc/composite-js';

Troubleshooting

Check:
  • captureEvents is not set to false
  • API key is correct and has event permissions
  • No JavaScript errors in console
  • Network tab shows requests to /ingest/e or /ingest/batch
  • beforeSend hook is not returning null
Ensure:
  • You call identify() after the user logs in
  • The distinct_id is consistent across sessions
  • You’re not calling reset() immediately after identify()
Check:
  • captureEvents is enabled (person updates are ignored when disabled)
  • For setPersonPropertiesOnce, the property may already exist
  • Verify the properties object contains valid values
Debug:
beforeSend: (event) => {
  console.log('Event before send:', event);
  return event;
}
Ensure your hook always returns the event or explicitly returns null.

Best Practices

Use Descriptive Names

Name events clearly: subscription_started not sub1

Set Properties Early

Use setPersonPropertiesOnce for attribution data on first visit

Identify Promptly

Call identify() as soon as the user logs in or signs up

Sanitize Data

Use beforeSend to strip sensitive data before sending

Next Steps