How to Set Up In-App Event Schemas (Best Practices)

Design robust event schemas for mobile attribution. Learn naming conventions, parameter standards, and implementation patterns that scale.

Justin Sampson
How to Set Up In-App Event Schemas (Best Practices)

How to Set Up In-App Event Schemas (Best Practices)

Poor event schemas create data debt that compounds over time. Once your app is live with thousands of users, fixing event naming or parameter structures becomes expensive and error-prone.

Getting the schema right before implementation saves months of cleanup work.

Here's how to design event tracking that scales from initial launch to millions of users.

Why Event Schemas Matter

An event schema defines:

  • Which user actions you track
  • How you name those events
  • What data you capture with each event
  • Data types and validation rules

Good schemas enable:

  • Consistent cross-platform analysis
  • Accurate attribution and optimization
  • Clean data warehouse integration
  • Easy debugging and troubleshooting

Bad schemas create:

  • Data fragmentation across platforms
  • Inconsistent reporting
  • Expensive migration and cleanup projects
  • Analysis paralysis when data doesn't match

Core Principles

1. Consistency Above All

Use identical naming across:

  • iOS and Android apps
  • MMP (Adjust, AppsFlyer, Branch)
  • Product analytics (Mixpanel, Amplitude)
  • Ad networks (Facebook, Google, TikTok)
  • Data warehouse

Bad:

  • iOS: "userRegistered"
  • Android: "user_registration"
  • MMP: "signup_complete"
  • Facebook: "CompleteRegistration"

Good:

  • All platforms: "registration_completed"

2. Be Specific, Not Generic

Event names should clearly describe what happened without requiring additional context.

Bad:

  • "button_clicked"
  • "action_completed"
  • "event_1"

Good:

  • "checkout_initiated"
  • "subscription_purchased"
  • "onboarding_completed"

3. Track Value, Not Activity

Focus on events that drive business decisions, not every user interaction.

Track in MMP:

  • Purchase completed
  • Subscription started
  • Trial activated
  • Registration completed

Don't track in MMP:

  • Screen viewed
  • Button tapped
  • App backgrounded
  • Settings changed

Save low-value events for product analytics tools like Mixpanel or Amplitude. MMPs charge per event—tracking everything gets expensive fast.

4. Design for the Future

Your schema should accommodate:

  • New product features
  • Additional platforms (web, tablet, TV)
  • International expansion
  • A/B testing variations

Build extensibility into naming conventions from the start.

Naming Conventions

Event Names

Use a consistent pattern. The most scalable approach is:

Format: object_action

Examples:

  • purchase_completed
  • subscription_started
  • trial_activated
  • cart_abandoned
  • content_shared

Alternative format: action_object

Examples:

  • completed_purchase
  • started_subscription
  • activated_trial

Choose one and stick with it. Mixing formats creates confusion.

Naming Rules

Use snake_case:

  • purchase_completed
  • purchaseCompleted
  • purchase-completed
  • PURCHASE_COMPLETED

Use past tense for completed actions:

  • registration_completed
  • registration_complete
  • register

Be specific about timing:

  • checkout_initiated (user started checkout)
  • checkout_completed (user finished checkout)
  • payment_submitted (payment processing started)
  • payment_confirmed (payment successful)

Avoid abbreviations:

  • registration_completed
  • reg_completed
  • user_reg

Exception: Universally understood abbreviations (ID, URL, API)

Parameter Names

Parameters (properties) follow the same rules as events.

Use snake_case:

  • product_id
  • productId

Be explicit:

  • revenue_usd
  • amount
  • total

Include units when relevant:

  • duration_seconds
  • duration
  • file_size_mb
  • file_size

Standard Event Schema Template

Core Events (Track in Every App)

1. Install

Event: app_installed
Platform: Auto-tracked by MMP
Parameters: None (MMP adds attribution data automatically)

2. First Open

Event: app_opened_first_time
Trigger: First app launch after install
Parameters:
  - platform: "ios" | "android"
  - app_version: String
  - os_version: String

3. Registration

Event: registration_completed
Trigger: User successfully creates account
Parameters:
  - user_id: String (your internal ID)
  - registration_method: "email" | "google" | "apple" | "facebook"
  - is_organic: Boolean

4. Purchase

Event: purchase_completed
Trigger: Successful purchase confirmation
Parameters:
  - revenue: Number (decimal, e.g., 9.99)
  - currency: String (ISO 4217, e.g., "USD")
  - transaction_id: String (unique, prevents duplicates)
  - product_id: String
  - product_category: String
  - quantity: Integer
  - is_first_purchase: Boolean

5. Subscription Started

Event: subscription_started
Trigger: User begins paid subscription
Parameters:
  - revenue: Number (subscription price)
  - currency: String
  - transaction_id: String
  - subscription_id: String
  - subscription_type: "monthly" | "yearly" | "weekly"
  - trial_period_days: Integer (0 if no trial)
  - is_renewal: Boolean

E-Commerce Events

Add to Cart

Event: cart_item_added
Parameters:
  - product_id: String
  - product_name: String
  - product_category: String
  - price: Number
  - currency: String
  - quantity: Integer

Checkout Initiated

Event: checkout_initiated
Parameters:
  - cart_value: Number
  - currency: String
  - item_count: Integer
  - cart_items: Array of product_ids

Checkout Completed

Event: checkout_completed
Parameters:
  - Same as purchase_completed
  - plus checkout_method: "credit_card" | "paypal" | "apple_pay" | etc.

Content/Media App Events

Content Viewed

Event: content_viewed
Parameters:
  - content_id: String
  - content_type: "article" | "video" | "podcast"
  - content_category: String
  - duration_seconds: Integer (for video/audio)

Content Completed

Event: content_completed
Parameters:
  - content_id: String
  - content_type: String
  - completion_percentage: Integer (100 for full completion)
  - time_spent_seconds: Integer

SaaS/Productivity App Events

Feature Activated

Event: feature_activated
Parameters:
  - feature_name: String
  - feature_tier: "free" | "premium" | "enterprise"
  - activation_source: "onboarding" | "settings" | "prompt"

Project Created

Event: project_created
Parameters:
  - project_id: String
  - project_type: String
  - template_used: String | null
  - is_first_project: Boolean

Data Types and Validation

Standard Data Types

String:

  • User IDs
  • Product IDs
  • Categories
  • Enums ("email" | "google" | "facebook")

Number (Decimal/Float):

  • Revenue
  • Prices
  • Percentages

Integer:

  • Quantities
  • Counts
  • Duration in seconds
  • Completion percentages

Boolean:

  • is_first_purchase
  • is_organic
  • is_renewal

Array:

  • List of product IDs
  • Feature flags
  • Tags/categories

Timestamp:

  • Usually auto-added by MMP
  • Use ISO 8601 format if manually tracking

Validation Rules

Revenue:

  • Type: Float/Decimal
  • Precision: 2 decimal places
  • Range: 0 to 999999.99
  • Never negative
  • Always include currency parameter

Currency:

  • Type: String
  • Format: ISO 4217 (uppercase)
  • Examples: "USD", "EUR", "GBP"
  • Never use symbols ($, €, £)

IDs:

  • Type: String (not integer—preserves leading zeros)
  • Never null or empty
  • Unique per entity
  • Consistent format (UUIDs, sequential IDs, etc.)

Enums:

  • Lowercase with underscores
  • Finite, documented set of values
  • Don't let users supply arbitrary values

Implementation Patterns

Centralized Event Tracking

Create a single tracking module that all parts of your app use.

iOS (Swift):

class AnalyticsManager {
    static let shared = AnalyticsManager()

    enum Event {
        case registrationCompleted(method: RegistrationMethod)
        case purchaseCompleted(revenue: Decimal, currency: String, productId: String, transactionId: String)
        case subscriptionStarted(revenue: Decimal, currency: String, type: SubscriptionType, transactionId: String)

        var name: String {
            switch self {
            case .registrationCompleted: return "registration_completed"
            case .purchaseCompleted: return "purchase_completed"
            case .subscriptionStarted: return "subscription_started"
            }
        }

        var parameters: [String: Any] {
            switch self {
            case .registrationCompleted(let method):
                return ["registration_method": method.rawValue]

            case .purchaseCompleted(let revenue, let currency, let productId, let transactionId):
                return [
                    "revenue": revenue,
                    "currency": currency,
                    "product_id": productId,
                    "transaction_id": transactionId
                ]

            case .subscriptionStarted(let revenue, let currency, let type, let transactionId):
                return [
                    "revenue": revenue,
                    "currency": currency,
                    "subscription_type": type.rawValue,
                    "transaction_id": transactionId
                ]
            }
        }
    }

    func track(_ event: Event) {
        // Track to MMP
        let adjustEvent = ADJEvent(eventToken: getEventToken(for: event.name))
        event.parameters.forEach { key, value in
            adjustEvent?.addCallbackParameter(key, value: "\(value)")
        }
        Adjust.trackEvent(adjustEvent)

        // Track to analytics
        Mixpanel.mainInstance().track(event: event.name, properties: event.parameters)
    }

    private func getEventToken(for eventName: String) -> String {
        // Map event names to MMP tokens
        // Centralized configuration prevents errors
        let eventTokens = [
            "registration_completed": "abc123",
            "purchase_completed": "def456",
            "subscription_started": "ghi789"
        ]
        return eventTokens[eventName] ?? ""
    }
}

enum RegistrationMethod: String {
    case email = "email"
    case google = "google"
    case apple = "apple"
    case facebook = "facebook"
}

enum SubscriptionType: String {
    case monthly = "monthly"
    case yearly = "yearly"
    case weekly = "weekly"
}

Usage:

AnalyticsManager.shared.track(
    .registrationCompleted(method: .email)
)

AnalyticsManager.shared.track(
    .purchaseCompleted(
        revenue: 9.99,
        currency: "USD",
        productId: "premium_monthly",
        transactionId: transaction.id
    )
)

Benefits:

  • Type safety (compiler catches errors)
  • Centralized event name definitions
  • Guaranteed consistency across tracking platforms
  • Easy to update all platforms from one location
  • Self-documenting

Event Mapping Configuration

Store event mapping in configuration files, not code.

events_config.json:

{
  "registration_completed": {
    "adjust_token": "abc123",
    "facebook_event": "CompleteRegistration",
    "google_event": "sign_up",
    "description": "User successfully created account",
    "parameters": {
      "user_id": "string",
      "registration_method": "enum[email,google,apple,facebook]"
    }
  },
  "purchase_completed": {
    "adjust_token": "def456",
    "facebook_event": "Purchase",
    "google_event": "purchase",
    "description": "User completed purchase",
    "parameters": {
      "revenue": "decimal",
      "currency": "string",
      "transaction_id": "string",
      "product_id": "string"
    }
  }
}

Benefits:

  • Non-developers can update event mappings
  • Easy to validate schema consistency
  • Can generate documentation automatically
  • Single source of truth

Documentation Template

Document every event you track. Use this template:

## Event: purchase_completed

**Description:** Fires when a user successfully completes a purchase

**Trigger:** After payment confirmation, before showing success screen

**Platforms:** iOS, Android, Web

**MMP Token:** def456

**Frequency:** Average 0.05 per user per day

**Parameters:**

| Name | Type | Required | Description | Example |
|------|------|----------|-------------|---------|
| revenue | Decimal | Yes | Purchase amount | 9.99 |
| currency | String | Yes | ISO 4217 code | "USD" |
| transaction_id | String | Yes | Unique transaction ID | "txn_abc123" |
| product_id | String | Yes | Product identifier | "premium_monthly" |
| product_category | String | No | Product category | "subscription" |
| is_first_purchase | Boolean | Yes | First purchase for user | true |

**Example Implementation:**

iOS:
```swift
AnalyticsManager.shared.track(
    .purchaseCompleted(
        revenue: 9.99,
        currency: "USD",
        productId: "premium_monthly",
        transactionId: transaction.id
    )
)

Notes:

  • Always use unique transaction_id to prevent duplicate revenue tracking
  • Currency must be uppercase ISO 4217
  • Revenue must include exactly 2 decimal places

## Common Mistakes to Avoid

### 1. Inconsistent Naming

**Problem:** Different naming across platforms

**Example:**
- iOS: `userCompletedPurchase`
- Android: `purchase_complete`
- MMP: `Purchase Completed`

**Fix:** Enforce snake_case everywhere, document in schema

### 2. Tracking Too Many Events

**Problem:** Tracking every user interaction

**Impact:**
- Higher MMP costs (pay per event)
- Data noise obscures important signals
- Harder to maintain and debug

**Fix:** Limit MMP tracking to 5-15 high-value conversion events

### 3. Missing Transaction IDs

**Problem:** Not using unique IDs for revenue events

**Impact:**
- Duplicate revenue tracking if event fires multiple times
- Inflated revenue reports
- Impossible to debug specific transactions

**Fix:** Always include transaction_id for revenue events

### 4. Dynamic Event Names

**Problem:** Building event names at runtime

**Example:**
```swift
// BAD
track(event: "purchase_\(productType)_completed")
// Results in: purchase_subscription_completed, purchase_addon_completed, etc.

Impact:

  • Unlimited event proliferation
  • Can't configure in MMP dashboard
  • Breaks reporting

Fix: Use parameters, not dynamic names

// GOOD
track(event: "purchase_completed", parameters: ["product_type": productType])

5. Arbitrary Parameter Values

Problem: Letting users supply freeform values

Example:

track(event: "registration_completed", parameters: ["method": userInputMethod])
// Could be anything: "Email", "GOOGLE", "fb", "Facebook Inc.", etc.

Impact:

  • Data fragmentation
  • Impossible to aggregate
  • Reporting breaks down

Fix: Use validated enums

enum RegistrationMethod: String {
    case email = "email"
    case google = "google"
    case facebook = "facebook"
}

Testing and Validation

Pre-Production Validation

1. Schema Documentation Review

  • All events documented with descriptions
  • Parameter types and validation rules defined
  • Required vs optional parameters marked
  • Example values provided

2. Code Review Checklist

  • Event names use snake_case
  • Event names match documentation exactly
  • Parameters match documented schema
  • Revenue events include transaction_id
  • Currency codes are uppercase ISO 4217
  • Enum values are validated

3. Test Event Firing

  • Events appear in MMP dashboard
  • Parameter values are correct types
  • Revenue amounts display correctly
  • Transaction IDs are unique
  • Events fire only once per action

Post-Production Monitoring

Weekly:

  • Check for unexpected event names (indicates schema violations)
  • Verify event volume matches user activity
  • Look for duplicate transaction IDs

Monthly:

  • Review parameter value distributions
  • Identify unused events (consider removing)
  • Check for new edge cases requiring schema updates

FAQs

What events should I track in my MMP?

Track high-value conversion events: install, first open, registration/signup, purchase, subscription, add to cart, and key feature usage. Avoid tracking low-value events like screen views or button clicks at the MMP level—use product analytics tools for those.

Should I use the same event names across all platforms?

Yes, absolutely. Use identical event names and parameter keys across iOS, Android, MMP, analytics platforms, and ad networks. This consistency enables cross-platform analysis and prevents data fragmentation. Document your schema and enforce it through code reviews.

How many events should I track?

Focus on 5-15 core conversion events. More isn't better—each event adds complexity and cost. Track events that directly influence marketing decisions and revenue optimization, not every user interaction.

Can I change event names after launch?

You can, but it's painful. Changing event names requires updating code, MMP configuration, ad network mappings, dashboards, and documentation. Historical data uses old names, creating reporting gaps. Get names right before launch.

Should I version my event schema?

For complex apps, yes. Add a schema_version parameter to events. This lets you evolve the schema while maintaining backward compatibility for analytics that span the transition period.


Event schemas are infrastructure. Like database schemas, they're expensive to change once you have production data flowing. Invest time in getting naming conventions, data types, and validation rules right before implementation, and you'll save months of cleanup work later.

event trackingMMPanalyticsevent schemadata architecture

Related Resources