Skip to main content

Event Design Principles

Structure Your Events Consistently

Use a consistent structure across your application:

Good Structure

{
  "name": "user_prompt",
  "session_id": "conv_abc123",
  "user_id": "user_456",
  "metadata": {
    "prompt": "actual user input"
  }
}

Avoid

{
  "name": "event",
  "metadata": {
    "data": "mixed content",
    "stuff": "unclear purpose"
  }
}

Maintain Session Continuity

The session_id connects events into a conversation journey. Turret auto-generates it if not provided, but you must persist and reuse it:
class TurretTracker {
  constructor(apiKey) {
    this.apiKey = apiKey;
    this.sessionId = null;
    this.userId = null;
  }

  async track(prompt) {
    const response = await fetch('https://api.useturret.com/track', {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
        'X-API-Key': this.apiKey
      },
      body: JSON.stringify({
        name: 'user_message',
        session_id: this.sessionId,  // null on first call
        user_id: this.userId,
        metadata: { prompt }
      })
    });

    const data = await response.json();
    // Store for subsequent messages
    this.sessionId = data.session_id;
    this.userId = data.user_id;
  }

  startNewConversation() {
    this.sessionId = null;  // Next track() gets new session_id
  }
}
Already have conversation IDs? Pass your existing conversation/thread ID as session_id - Turret will use it directly instead of generating one.

Focus on User-Generated Text

Turret works best with free-form text that users actually write or say: Excellent for Turret:
  • User prompts and questions
  • Search queries
  • Support requests
  • Feedback and comments
Less valuable for Turret:
  • System-generated messages
  • Enum values or categories
  • Technical logs
  • Numeric data

Session ID Best Practices

Option 1: Let Turret Generate IDs

If you don’t have existing conversation IDs, let Turret generate them:
// First message - no session_id
const response1 = await turret.track({ prompt: 'What pricing plans do you offer?' });
const sessionId = response1.session_id;  // Store this!

// Subsequent messages - include session_id
await turret.track({ prompt: 'What features are in the pro plan?', session_id: sessionId });
await turret.track({ prompt: 'Can I get a discount?', session_id: sessionId });
This creates a journey: Pricing → Features → Discounts

Option 2: Use Your Own IDs

If you already have conversation IDs (most apps do), pass them directly:
// Your app already has a conversation ID
const myConversationId = conversation.id;

// Pass it to Turret - it uses your ID, not a generated one
await turret.track({
  prompt: 'What pricing plans do you offer?',
  session_id: myConversationId,
  user_id: currentUser.id
});

What to Avoid

// BAD: Different session_id for each message (breaks journeys)
await turret.track({ prompt: 'Question 1' });  // Gets session A
await turret.track({ prompt: 'Question 2' });  // Gets session B - NOT connected!

// BAD: Hardcoded session_id (mixes all users together)
await turret.track({ prompt: 'Question', session_id: 'default' });

// GOOD: Store and reuse session_id
const { session_id } = await turret.track({ prompt: 'Question 1' });
await turret.track({ prompt: 'Question 2', session_id });  // Connected!

Cross-Session Analysis with user_id

Track the same user across multiple conversations:
// Monday: User starts a conversation (new session)
const { user_id } = await turret.track({ prompt: 'How do I set up the integration?' });
// Store user_id with your user record

// Wednesday: Same user, new conversation
await turret.track({
  prompt: 'The integration stopped working',
  user_id: storedUserId  // Same user, but new session_id (omitted = auto-generated)
});
This reveals patterns like: users who ask about setup often return with issues.

Metadata Design

Keep Metadata Simple

Focus on the text you want to cluster:
{
  "name": "user_prompt",
  "session_id": "conv_abc123",
  "metadata": {
    "prompt": "How do I integrate your API with my React app?"
  }
}

Add Context When Helpful

You can include additional metadata for context (only the prompt field is clustered):
{
  "name": "user_prompt",
  "session_id": "conv_abc123",
  "user_id": "user_456",
  "metadata": {
    "prompt": "How do I integrate your API with my React app?",
    "detected_language": "english",
    "platform": "web"
  }
}

Use Natural Language

Store text as users actually wrote it:

Preserve Original

{
  "prompt": "hey, can u help me fix this bug?"
}

Avoid Over-Processing

{
  "prompt": "HELP FIX BUG"
}

Event Timing

Track at the Right Moments

Capture events when you have the most complete information:
Track immediately after user submits text (prompts, questions, search queries)
Track after generating a response if you want to analyze AI outputs
Track when users provide explicit feedback or ratings

Data Quality

Validate Before Sending

Ensure your events contain meaningful data:
function validateEvent(name, sessionId, metadata) {
  // Check for required fields
  if (!name || !metadata) return false;
  
  // Ensure metadata has text content
  const hasText = Object.values(metadata).some(value => 
    typeof value === 'string' && value.trim().length > 0
  );
  
  if (!hasText) return false;
  
  // Warn if session_id is missing (recommended field)
  if (!sessionId) {
    console.warn('session_id missing - journey tracking disabled');
  }
  
  return true;
}

Handle Sensitive Data

Be careful with personally identifiable information (PII):
function sanitizeMetadata(metadata) {
  return Object.fromEntries(
    Object.entries(metadata).map(([key, value]) => [
      key,
      typeof value === 'string' ? 
        value.replace(/\b\d{3}-\d{2}-\d{4}\b/g, '[SSN]')
             .replace(/\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,}\b/g, '[EMAIL]')
        : value
    ])
  );
}

Performance Optimization

Use Async Tracking

Don’t block your main application flow:
async function trackEventAsync(name, sessionId, userId, metadata) {
  // Use setTimeout to avoid blocking
  setTimeout(async () => {
    try {
      await trackEvent(name, sessionId, userId, metadata);
    } catch (error) {
      console.error('Event tracking failed:', error);
      // Don't let tracking failures break your app
    }
  }, 0);
}

Implement Circuit Breakers

Protect your application from API failures:
class EventTracker {
  constructor() {
    this.failures = 0;
    this.maxFailures = 5;
    this.resetTime = 60000; // 1 minute
    this.lastFailureTime = 0;
  }
  
  async trackEvent(name, sessionId, userId, metadata) {
    if (this.isCircuitOpen()) {
      console.log('Circuit breaker open, skipping event');
      return;
    }
    
    try {
      await this.sendEvent(name, sessionId, userId, metadata);
      this.onSuccess();
    } catch (error) {
      this.onFailure();
      throw error;
    }
  }
  
  isCircuitOpen() {
    return this.failures >= this.maxFailures && 
           Date.now() - this.lastFailureTime < this.resetTime;
  }
  
  onSuccess() {
    this.failures = 0;
  }
  
  onFailure() {
    this.failures++;
    this.lastFailureTime = Date.now();
  }
}

Analytics Strategy

Start Simple, Evolve

Begin with basic events and add complexity over time: Phase 1: Minimal (Just Track Messages)
{
  "name": "user_message",
  "metadata": {
    "prompt": "user's actual question or request"
  }
}
Store the returned session_id for subsequent messages. Phase 2: Connect to Your User System
{
  "name": "user_message",
  "session_id": "your-conversation-id",
  "user_id": "your-user-id",
  "metadata": {
    "prompt": "user's actual question or request"
  }
}
Phase 3: Additional Context
{
  "name": "user_message",
  "session_id": "your-conversation-id",
  "user_id": "your-user-id",
  "metadata": {
    "prompt": "user's actual question or request",
    "platform": "mobile",
    "language": "en"
  }
}

Monitor Data Quality

Regularly check that your events are providing value:
  • Topic diversity: Are you seeing varied, meaningful topics?
  • Journey insights: Are session_ids producing useful journey patterns?
  • Event distribution: Are some event types dominating?
  • Actionability: Can you make product decisions from the insights?

Common Patterns

The Chat Application Pattern

Track each message in a conversation:
class ChatTracker {
  constructor(turretApiKey) {
    this.turret = new TurretClient(turretApiKey);
  }

  async onUserMessage(userMessage) {
    // Track the user's message
    await this.turret.track({
      name: 'user_message',
      session_id: this.sessionId,  // null on first message
      user_id: this.userId,
      metadata: { prompt: userMessage }
    }).then(data => {
      this.sessionId = data.session_id;
      this.userId = data.user_id;
    });
  }

  startNewConversation() {
    this.sessionId = null;  // New conversation = new session
  }
}

The Search Application Pattern

Track search queries - each search session is a conversation:
// Each search session gets its own session_id
const { session_id } = await turret.track({
  name: 'search_query',
  metadata: { query: searchTerm }
});

// If user refines their search, include session_id
await turret.track({
  name: 'search_query',
  session_id,
  metadata: { query: refinedSearchTerm }
});

The Support Ticket Pattern

Use ticket ID as session_id for easy correlation:
await turret.track({
  name: 'support_message',
  session_id: ticketId,      // Your existing ticket ID
  user_id: customerId,       // Your existing customer ID
  metadata: { prompt: customerMessage }
});

Measuring Success

Key Metrics to Track

  • Topic insights actioned: How many topic discoveries lead to product changes?
  • Journey optimization: Have journey insights improved user flows?
  • User satisfaction correlation: Do insights correlate with satisfaction improvements?
  • Support ticket reduction: Are you catching issues before they become support requests?

Regular Review Process

  1. Weekly: Review new and growing topics, check journey patterns
  2. Monthly: Analyze topic trends and journey correlations
  3. Quarterly: Assess impact of insights-driven decisions
  4. Annually: Review overall analytics strategy and goals
The most successful teams treat Turret as a continuous insight engine. Combining topic clustering with journey tracking reveals powerful patterns that neither could surface alone.