Event Schema
Complete specification for LoopKit event structure and data types.
Event Structure
Every LoopKit event follows this JSON schema:
{
"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
type PropertyValue = string | number | boolean | null;
interface EventProperties {
[key: string]: PropertyValue;
}
String Values:
{
"method": "email",
"source": "homepage",
"feature": "api_integration"
}
Numeric Values:
{
"duration": 120,
"price": 29.99,
"count": 5
}
Boolean Values:
{
"success": true,
"is_premium": false,
"first_time": true
}
Null Values:
{
"referrer": null,
"campaign": null
}
Unsupported Types
❌ Arrays:
{
"tags": ["javascript", "tutorial"] // Not supported
}
✅ Alternative - Flatten:
{
"tag_1": "javascript",
"tag_2": "tutorial",
"tag_count": 2
}
❌ Nested Objects:
{
"user": {
"profile": {
"name": "John"
}
}
}
✅ Alternative - Flatten:
{
"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:
{
"user_plan": "enterprise",
"time_spent": 180,
"success": true,
"feature_name": "api_integration",
"error_message": "Network timeout"
}
❌ Avoid:
{
"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
{
"user_id": "user_123",
"user_plan": "pro",
"user_type": "admin",
"team_id": "team_456",
"team_size": 12
}
Success/Failure
{
"success": true,
"error_code": "NETWORK_TIMEOUT",
"error_message": "Request timed out after 30s"
}
Timing
{
"duration": 180, // seconds
"time_to_complete": 45, // seconds
"session_duration": 1800 // seconds
}
Source/Attribution
{
"source": "homepage",
"referrer": "https://google.com",
"utm_campaign": "summer_sale",
"utm_medium": "email"
}
Feature/Content
{
"feature": "api_integration",
"feature_type": "integration",
"content_type": "blog_post",
"category": "tutorials"
}
System Properties
Web Applications
{
"system": {
"platform": "web",
"browser": "Chrome",
"browser_version": "121.0.0",
"device_type": "desktop",
"screen_resolution": "1920x1080",
"user_agent": "Mozilla/5.0..."
}
}
Mobile Applications
{
"system": {
"platform": "ios",
"os_version": "17.2",
"app_version": "2.1.0",
"device_model": "iPhone15,2",
"network_type": "wifi"
}
}
Unity Games
{
"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:
{
"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:
{
"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:
{
"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
// 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
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
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
{
"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
{
"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
{
"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:
{
"$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:
// ✅ 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:
// ✅ 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:
// ✅ 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:
{
"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 numberplatform
: Platform identifier (web, unity, desktop)timezone
: User's timezonelanguage
: User's language preference