Skip to content

Event Schema

Complete specification for LoopKit event structure and data types.

Event Structure

Every LoopKit event follows this JSON schema:

json
{
  "anonymousId": "string (required)",
  "userId": "string (optional)",
  "name": "string (required)",
  "properties": "object (optional)",
  "system": "object (optional)",
  "timestamp": "string (required, ISO 8601)"
}

Required Fields

anonymousId

  • Type: string
  • Required: Yes
  • Description: Anonymous identifier for the user/session
  • Format: Any string, but should be consistent for the same user/session
  • Examples: "anon_123", "uuid-v4", "session_abc"

name

  • Type: string
  • Required: Yes
  • Description: Name of the event
  • Format: snake_case recommended
  • Examples: "user_signup", "feature_used", "payment_completed"

timestamp

  • Type: string
  • Required: Yes
  • Description: When the event occurred
  • Format: ISO 8601 with timezone
  • Examples: "2025-01-15T10:30:00Z", "2025-01-15T10:30:00.123Z"

Optional Fields

userId

  • Type: string
  • Required: No
  • Description: Authenticated user identifier
  • When to include: After user logs in or is identified
  • Examples: "user_123", "auth0|abc123"

properties

  • Type: object
  • Required: No
  • Description: Event-specific data and context
  • Structure: Flat object with string keys

system

  • Type: object
  • Required: No
  • Description: System/platform information
  • Common fields: platform, version, device, browser

Property Data Types

Supported Types

typescript
type PropertyValue = string | number | boolean | null;

interface EventProperties {
  [key: string]: PropertyValue;
}

String Values:

json
{
  "method": "email",
  "source": "homepage",
  "feature": "api_integration"
}

Numeric Values:

json
{
  "duration": 120,
  "price": 29.99,
  "count": 5
}

Boolean Values:

json
{
  "success": true,
  "is_premium": false,
  "first_time": true
}

Null Values:

json
{
  "referrer": null,
  "campaign": null
}

Unsupported Types

❌ Arrays:

json
{
  "tags": ["javascript", "tutorial"] // Not supported
}

✅ Alternative - Flatten:

json
{
  "tag_1": "javascript",
  "tag_2": "tutorial",
  "tag_count": 2
}

❌ Nested Objects:

json
{
  "user": {
    "profile": {
      "name": "John"
    }
  }
}

✅ Alternative - Flatten:

json
{
  "user_profile_name": "John"
}

Naming Conventions

Event Names

Format: verb_noun in snake_case

✅ Good Examples:

user_signup
feature_used
report_generated
payment_completed
level_started
subscription_upgraded

❌ Avoid:

click              // Too generic
userSignup         // Use snake_case
User Signup        // No spaces
feature-used       // Use underscores
finished_report    // Inconsistent verb tense

Property Names

Format: snake_case with descriptive names

✅ Good Examples:

json
{
  "user_plan": "enterprise",
  "time_spent": 180,
  "success": true,
  "feature_name": "api_integration",
  "error_message": "Network timeout"
}

❌ Avoid:

json
{
  "plan": "enterprise", // Too generic
  "timeSpent": 180, // Use snake_case
  "isSuccess": true, // Just "success"
  "feature": "api_integration", // Be specific
  "error": "Network timeout" // Be specific
}

Standard Property Names

Use these standardized property names for consistency:

User Context

json
{
  "user_id": "user_123",
  "user_plan": "pro",
  "user_type": "admin",
  "team_id": "team_456",
  "team_size": 12
}

Success/Failure

json
{
  "success": true,
  "error_code": "NETWORK_TIMEOUT",
  "error_message": "Request timed out after 30s"
}

Timing

json
{
  "duration": 180, // seconds
  "time_to_complete": 45, // seconds
  "session_duration": 1800 // seconds
}

Source/Attribution

json
{
  "source": "homepage",
  "referrer": "https://google.com",
  "utm_campaign": "summer_sale",
  "utm_medium": "email"
}

Feature/Content

json
{
  "feature": "api_integration",
  "feature_type": "integration",
  "content_type": "blog_post",
  "category": "tutorials"
}

System Properties

Web Applications

json
{
  "system": {
    "platform": "web",
    "browser": "Chrome",
    "browser_version": "121.0.0",
    "device_type": "desktop",
    "screen_resolution": "1920x1080",
    "user_agent": "Mozilla/5.0..."
  }
}

Mobile Applications

json
{
  "system": {
    "platform": "ios",
    "os_version": "17.2",
    "app_version": "2.1.0",
    "device_model": "iPhone15,2",
    "network_type": "wifi"
  }
}

Unity Games

json
{
  "system": {
    "platform": "unity",
    "unity_version": "2023.2.0f1",
    "game_version": "1.5.2",
    "device_type": "mobile",
    "graphics_device": "Metal"
  }
}

Event Categories

Lifecycle Events

Events tracking user journey stages:

json
{
  "name": "user_signup",
  "properties": {
    "method": "email",
    "source": "homepage"
  }
}

{
  "name": "onboarding_completed",
  "properties": {
    "completion_time": 180,
    "steps_completed": 5
  }
}

{
  "name": "user_churned",
  "properties": {
    "days_inactive": 30,
    "last_feature_used": "reports"
  }
}

Feature Usage Events

Events tracking feature interaction:

json
{
  "name": "feature_used",
  "properties": {
    "feature": "api_integration",
    "success": true,
    "duration": 45
  }
}

{
  "name": "report_generated",
  "properties": {
    "report_type": "analytics",
    "data_range": "30d",
    "export_format": "csv"
  }
}

Business Events

Events tracking revenue and conversion:

json
{
  "name": "subscription_upgraded",
  "properties": {
    "from_plan": "pro",
    "to_plan": "enterprise",
    "price_difference": 50
  }
}

{
  "name": "payment_completed",
  "properties": {
    "amount": 99.00,
    "currency": "USD",
    "payment_method": "card"
  }
}

Validation Rules

Event Name Validation

javascript
// Valid event names
const isValidEventName = (name) => {
  // 1. Must be string
  if (typeof name !== 'string') return false;

  // 2. Must be snake_case
  if (!/^[a-z0-9_]+$/.test(name)) return false;

  // 3. Must not start/end with underscore
  if (name.startsWith('_') || name.endsWith('_')) return false;

  // 4. Must not have consecutive underscores
  if (name.includes('__')) return false;

  // 5. Length limits
  if (name.length < 1 || name.length > 100) return false;

  return true;
};

Property Validation

javascript
const isValidProperty = (key, value) => {
  // Key validation
  if (typeof key !== 'string') return false;
  if (key.length > 100) return false;
  if (!/^[a-z0-9_]+$/.test(key)) return false;

  // Value validation
  const validTypes = ['string', 'number', 'boolean'];
  if (value !== null && !validTypes.includes(typeof value)) return false;

  // String length limit
  if (typeof value === 'string' && value.length > 1000) return false;

  return true;
};

Timestamp Validation

javascript
const isValidTimestamp = (timestamp) => {
  try {
    const date = new Date(timestamp);

    // Must be valid date
    if (isNaN(date.getTime())) return false;

    // Must be ISO 8601 format
    if (!/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(\.\d{3})?Z?$/.test(timestamp))
      return false;

    // Must not be too far in the future
    const now = new Date();
    const maxFuture = new Date(now.getTime() + 24 * 60 * 60 * 1000); // 24 hours
    if (date > maxFuture) return false;

    return true;
  } catch (error) {
    return false;
  }
};

Complete Schema Examples

Basic Web Event

json
{
  "anonymousId": "anon_abc123",
  "userId": "user_456",
  "name": "page_view",
  "properties": {
    "page": "/dashboard",
    "section": "analytics",
    "previous_page": "/home",
    "time_on_page": 45
  },
  "system": {
    "platform": "web",
    "browser": "Chrome",
    "device_type": "desktop"
  },
  "timestamp": "2025-01-15T10:30:00Z"
}

Mobile Game Event

json
{
  "anonymousId": "device_xyz789",
  "userId": "player_123",
  "name": "level_completed",
  "properties": {
    "level": 15,
    "world": "forest",
    "score": 95000,
    "time_seconds": 245,
    "deaths": 2,
    "power_ups_used": 3
  },
  "system": {
    "platform": "unity",
    "game_version": "1.5.2",
    "device_type": "mobile",
    "os": "iOS"
  },
  "timestamp": "2025-01-15T10:30:00Z"
}

SaaS Feature Event

json
{
  "anonymousId": "session_def456",
  "userId": "user_789",
  "name": "integration_setup",
  "properties": {
    "integration_type": "slack",
    "setup_method": "guided",
    "time_to_complete": 180,
    "success": true,
    "user_plan": "enterprise",
    "team_size": 25
  },
  "system": {
    "platform": "web",
    "app_version": "2.1.0"
  },
  "timestamp": "2025-01-15T10:30:00Z"
}

JSON Schema Definition

For programmatic validation, here's the JSON Schema:

json
{
  "$schema": "http://json-schema.org/draft-07/schema#",
  "type": "object",
  "required": ["anonymousId", "name", "timestamp"],
  "properties": {
    "anonymousId": {
      "type": "string",
      "minLength": 1,
      "maxLength": 100
    },
    "userId": {
      "type": "string",
      "minLength": 1,
      "maxLength": 100
    },
    "name": {
      "type": "string",
      "pattern": "^[a-z0-9_]+$",
      "minLength": 1,
      "maxLength": 100
    },
    "properties": {
      "type": "object",
      "patternProperties": {
        "^[a-z0-9_]+$": {
          "oneOf": [
            { "type": "string", "maxLength": 1000 },
            { "type": "number" },
            { "type": "boolean" },
            { "type": "null" }
          ]
        }
      },
      "additionalProperties": false
    },
    "system": {
      "type": "object",
      "patternProperties": {
        "^[a-z0-9_]+$": {
          "type": "string",
          "maxLength": 1000
        }
      },
      "additionalProperties": false
    },
    "timestamp": {
      "type": "string",
      "format": "date-time"
    }
  },
  "additionalProperties": false
}

Best Practices

1. Consistent Naming

Use the same event name for the same action across your app:

javascript
// ✅ Consistent
track('report_generated', { type: 'analytics' });
track('report_generated', { type: 'billing' });

// ❌ Inconsistent
track('analytics_report_created', { type: 'analytics' });
track('billing_report_generated', { type: 'billing' });

2. Rich Context

Include relevant context for better insights:

javascript
// ✅ Rich context
track('feature_used', {
  feature: 'api_integration',
  user_plan: 'enterprise',
  team_size: 25,
  success: true,
  duration: 45,
  first_time: false,
});

// ❌ Minimal context
track('feature_used', {
  feature: 'api_integration',
});

3. Standard Units

Use consistent units across events:

javascript
// ✅ Consistent units (seconds)
track('video_watched', { duration: 180 });
track('session_duration', { duration: 1800 });

// ❌ Mixed units
track('video_watched', { duration_minutes: 3 });
track('session_duration', { duration_seconds: 1800 });

Next Steps


Questions about event structure? Join our Discord for developer support.

System Context (Optional)

Additional context about the app or system:

json
{
  "system": {
    "app_version": "1.2.3",
    "platform": "web",
    "user_agent": "Mozilla/5.0...",
    "screen_resolution": "1920x1080",
    "timezone": "America/New_York"
  }
}

Recommended Fields:

  • app_version: Your app's version number
  • platform: Platform identifier (web, unity, desktop)
  • timezone: User's timezone
  • language: User's language preference