Skip to content

Understanding Event Schemas & Parameters

Parameters are how you see everything. If you don’t understand your event schema, you can’t read your data.


Event Schema: The structure/format of data captured with each event

Every event in your system has:

  • Event name (e.g., conv_landing_page_view, purchase_completed)
  • User identifier (e.g., user_id, distinct_id, email)
  • Timestamp
  • Properties (event-specific data)
  • Parameters (tracking/attribution data)

The parameters are the most important part for attribution.


Captured from URL when user lands:

{
"utm_source": "fb", // Traffic source (fb, google, email, etc.)
"utm_medium": "cpc", // Medium (cpc, social, email, organic)
"utm_campaign": "tirzepatide-q4", // Campaign name
"utm_term": "weight-loss", // Keyword/targeting term
"utm_content": "video-ad-v2" // Ad variation
}

Facebook:

{
"campaign_id": "120236311971890504",
"ad_id": "120233281654680682",
"adset_id": "120233281060550682",
"fbp": "fb.1.1764081009618.5799537734763449309",
"fbc": "fb.1.1764081009618..."
}

Google Ads:

{
"gclid": "CjwKCAiA...",
"campaign_id": "22740491004",
"g_campaignid": "22740491004"
}

Affiliate/Channel:

{
"affid": "1000", // Traffic source identifier
"aff_sub": "fb", // Sub-affiliate
"tid": "1" // Tracking ID
}

Email Tracking:

{
"source": "email_crm",
"campaign": "direct-crm-ticket",
"email_id": "msg_abc123",
"email_campaign_id": "camp_xyz789"
}

SMS Tracking:

{
"sms_campaign": "sms_prospect_nov_20",
"sms_message_id": "msg_sms_xyz",
"sms_link_id": "link_123"
}
{
"$referrer": "https://google.com/search?q=...",
"$initial_referrer": "https://facebook.com",
"landing_page": "/medication-tirzepatide-discount-01",
"landing_page_source": "https://landing.fh.co/medication-tirzepatide-discount-01?utm_source=...",
"$initial_host": "landing.fh.co",
"viewport_width": 411,
"viewport_height": 871
}

Event: Landing page view

Parameters captured:

{
"event": "conv_landing_page_view",
"distinct_id": "019aa24361a57f03928122b309e186f6",
"timestamp": "2025-11-20T14:23:45Z",
// The footprint
"utm_source": "fb",
"utm_medium": "cpc",
"campaign_id": "120236311971890504",
"ad_id": "120233281654680682",
"adset_id": "120233281060550682",
"affid": "1000",
"fbp": "fb.1.1764081009618.5799537734763449309",
"landing_page": "/medication-tirzepatide-discount-01"
}

Reading the footprint:

  • utm_source: fb → Facebook traffic
  • campaign_id present → Specific campaign
  • ad_id present → Can attribute to specific ad
  • affid: 1000 → Internal tracking shows this is our main Facebook channel
  • fbp present → Facebook pixel tracking active

What this tells you: User came from Facebook campaign 120236311971890504, ad 120233281654680682, through affid 1000, landed on discount-01 page.


Example 2: SMS Retargeting (Disguised as Google)

Section titled “Example 2: SMS Retargeting (Disguised as Google)”

Event: Purchase

Parameters captured:

{
"event": "purchase_completed",
"distinct_id": "019a974d0bb877f19905e4e10c3d5892",
"timestamp": "2025-11-20T16:52:33Z",
"amount": 499,
// Surface-level label
"source": "google ads", // MISLEADING
// The actual footprint
"g_campaignid": "22740491004", // Original Google campaign
"sms_campaign": "sms_prospect_nov_20", // Current SMS campaign
"sms_message_id": "msg_sms_xyz", // SMS identifier
"original_utm_source": "google", // First-touch
"original_campaign_id": "22740491004" // First-touch campaign
}

Reading the footprint:

  • ⚠️ Surface source says “google ads” BUT
  • sms_campaign present → This is SMS retargeting
  • sms_message_id present → Confirms SMS click
  • original_utm_source: google → User ORIGINALLY came from Google
  • original_campaign_id → Preserve first-touch attribution

What this tells you: User originally came from Google Ads campaign 22740491004, was retargeted via SMS campaign “sms_prospect_nov_20”, clicked SMS link and purchased. Attribution goes to Google campaign (first-touch).

Without reading parameters: “This is a Google Ads sale” ✅ Correct attribution But without SMS parameters: You wouldn’t know SMS retargeting was involved


Event: Purchase

Parameters captured:

{
"event": "purchase_completed",
"distinct_id": "bceb8c64219a4188bb78bf144f20363c",
"timestamp": "2025-11-20T18:15:22Z",
"amount": 499,
// Current source
"source": "email_crm",
"campaign": "direct-crm-ticket",
"email_id": "msg_abc123",
// Original source (preserved)
"original_utm_source": "fb",
"original_campaign_id": "120236311971890504",
"original_ad_id": "120233281654680682",
"first_seen": "2025-11-15T10:23:45Z"
}

Reading the footprint:

  • ✅ Current source: email_crm (from CRM system)
  • ✅ Campaign: direct-crm-ticket (specific email campaign)
  • ✅ Original source: Facebook campaign 120236311971890504
  • ✅ First seen: 5 days ago

What this tells you: User originally came from Facebook ad 5 days ago, was nurtured via CRM email, clicked email and purchased. Attribution goes to Facebook campaign.


{
"event": "purchase_completed",
"distinct_id": "019aa24361a57f03928122b309e186f6",
"timestamp": "2025-11-20T14:23:45.000Z",
"properties": {
"$current_url": "https://landing.fh.co/checkout/complete",
"$referrer": "https://landing.fh.co/checkout",
"amount": 499,
"product": "Tirzepatide Subscription",
// Attribution parameters
"utm_source": "fb",
"utm_campaign": "tirzepatide-q4",
"campaign_id": "120236311971890504",
"affid": "1000"
},
"$set": {
"email": "user@example.com"
}
}

Key fields:

  • distinct_id: User identifier
  • properties: All event data including parameters
  • $set: User properties to store on profile
{
"user_id": "019aa24361a57f03928122b309e186f6",
"email": "user@example.com",
"conversion": {
"amount": 499,
"timestamp": "2025-11-20T14:23:45Z",
"transaction_id": "txn_abc123"
},
"attribution": {
"first_touch": {
"source": "fb",
"campaign_id": "120236311971890504",
"ad_id": "120233281654680682",
"timestamp": "2025-11-15T10:23:45Z"
},
"last_touch": {
"source": "email_crm",
"campaign": "direct-crm-ticket",
"timestamp": "2025-11-20T14:20:15Z"
},
"touchpoints": [
{ "source": "fb", "timestamp": "2025-11-15T10:23:45Z" },
{ "source": "sms", "timestamp": "2025-11-18T16:30:12Z" },
{ "source": "email_crm", "timestamp": "2025-11-20T14:20:15Z" }
]
}
}

Key fields:

  • first_touch: Original attribution (most important)
  • last_touch: Final touchpoint before conversion
  • touchpoints: All interactions in sequence

Pull 10-20 events from your system:

  • Landing page views
  • Lead captures
  • Purchases
  • Any key conversion events

Look at what’s captured:

  • User identifiers (user_id, distinct_id, email)
  • UTM parameters (utm_source, utm_medium, etc.)
  • Platform IDs (campaign_id, ad_id, gclid, fbp)
  • Custom parameters (affid, tid, etc.)
  • Session data (referrer, landing_page, device info)

Example findings:

  • “We capture affid - values are 1000 (Facebook), 1001 (Google), 1002 (Organic)”
  • “We have campaign_id for all paid campaigns”
  • “Email campaigns have source: email_crm and campaign field”
  • “SMS has sms_campaign and sms_message_id
Footprint Recognition:
affid = 1000 + utm_source = fb → Facebook traffic
affid = 1001 + gclid present → Google Ads traffic
source = email_crm → Email retargeting
sms_campaign present → SMS retargeting
affid = null + utm_source = null → Direct/organic

Now when you see data:

  • Row with affid 1000 → “This is Facebook”
  • Row with sms_campaign → “This is SMS retargeting”
  • Row with email_crm → “This is email nurture”

No meetings needed. Just observe and apply.


Wrong: “Source says ‘Google Ads’, so it’s a Google Ads sale”

Right: “Source says ‘Google Ads’, but parameters show sms_campaign → It’s SMS retargeting from a user who originally came from Google”

Wrong: “This sale has no affid, so we don’t know where it came from”

Right: “No affid, but has email_id parameter → It’s email retargeting. Check original UTM parameters to see first-touch.”

Wrong: “User converted from email, attribute to email”

Right: “User converted from email, but email was sent to lead acquired from Facebook campaign X → Attribute to Facebook”


When analyzing sales/conversions:

  1. Export data with all parameters
  2. Read the footprints (don’t trust source labels)
  3. Look for:
    • Original UTM parameters (first-touch)
    • Current source parameters (last-touch)
    • Platform IDs (campaign_id, ad_id for granular attribution)
    • Retargeting markers (sms_campaign, email_id)
  4. Attribute to first-touch (original acquisition source)
  5. Note multi-touch journey (SMS/email amplification)

Time required: 1-5 minutes once you understand your schema


If you don’t understand your event footprints, you’re a bad hunter.

Every event leaves traces (parameters). These traces tell you:

  • Where user came from
  • How they navigated
  • What campaigns drove them
  • Full multi-touch journey

Learn to read the footprints once, then use them forever.

No advanced tools required. Just observation and application.


This is what real event data looks like:

{
"content_type": "product",
"currency": "USD",
"fromGTM": "true",
"referrer": "",
"session_id": "TmtSzXXE2P0YSdxW1rkmygHDmDMsiWKn",
"timestamp": "2025-11-20T16:42:48.209Z",
"url": "https://gtm-msr.appspot.com/render?id=GTM-MGBZ7NKD"
}
{
"_gl": "1*17a11gh*_gcl_au*MjExMDAxODM2NC4xNzYzNjU4OTE2",
"ad_id": "120236311972130504",
"adset_id": "120236311972120504",
"aff_sub": "fb",
"affid": "1000",
"campaign_id": "120236311971890504",
"content_ids": ["bundle-micro-ol-itirz-monthly-50off"],
"content_type": "product",
"currency": "USD",
"email": "user@example.com",
"fbclid": "IwZXh0bgNhZW0BMABhZGlkAasqbG8Xa4hzcnRjBmFwcF9pZAo2NjI4NTY4Mzc5AAEexJyKeui8f7l3JN25hswCDqja7L_nNmQyUL-0Qel1rBwPrcPg6JuhIXNzJJI_aem_xX2ZI4lIsfVcEUocCVYP9Q",
"posthog_distinct_id": "019aa243-61a5-7f03-9281-22b309e186f6",
"posthog_session_id": "019aa243-61a4-7d5e-99c9-593206fd056d",
"transaction_id": "d9c26d5e-f19c-4f71-8712-21188b02aa42",
"utm_campaign": "120236311971890504",
"utm_medium": "cpc",
"utm_source": "fb"
}

It’s messy. It’s ugly. It’s full of IDs and codes.

This is normal. This is the core of what you do.


The Core Principle: IDs, Affids, UTMs Are Everything

Section titled “The Core Principle: IDs, Affids, UTMs Are Everything”

These ugly parameters ARE the core:

  • affid: 1000, 1001, 1002 → Traffic source identifier
  • campaign_id: 120236311971890504 → Specific campaign
  • ad_id: 120236311972130504 → Specific ad
  • utm_source: fb, google → Platform
  • utm_campaign: Campaign name
  • posthog_distinct_id: User identifier
  • fbclid: Facebook Click ID
  • transaction_id: Purchase identifier

Everything else (charts, dashboards, reports) is just a consequence of understanding these.

If you understand the parameters, you don’t need anything else.


{
"content_type": "product",
"currency": "USD",
"fromGTM": "true",
"referrer": "",
"session_id": "TmtSzXXE2P0YSdxW1rkmygHDmDMsiWKn",
"timestamp": "2025-11-20T16:42:48.209Z",
"url": "https://gtm-msr.appspot.com/render?id=GTM-MGBZ7NKD"
}

Reading the footprint:

  • fromGTM: true → Tracked via Google Tag Manager
  • url: gtm-msr.appspot.com → Google Tag Manager measurement server
  • Has session_id but not much else
  • Minimal footprint

What to do: Google “gtm-msr.appspot.com” → Takes 1 minute → Learn this is Google’s way of checking traffic

Event 2: Facebook Sale with Full Attribution

Section titled “Event 2: Facebook Sale with Full Attribution”
{
"_gl": "1*17a11gh*_gcl_au*MjExMDAxODM2NC4xNzYzNjU4OTE2",
"ad_id": "120236311972130504",
"adset_id": "120236311972120504",
"aff_sub": "fb",
"affid": "1000",
"campaign_id": "120236311971890504",
"content_ids": ["bundle-micro-ol-itirz-monthly-50off"],
"content_type": "product",
"currency": "USD",
"email": "user@example.com",
"fbclid": "IwZXh0bgNhZW0BMAAZG...",
"posthog_distinct_id": "019aa243-61a5-7f03-9281-22b309e186f6",
"posthog_session_id": "019aa243-61a4-7d5e-99c9-593206fd056d",
"transaction_id": "d9c26d5e-f19c-4f71-8712-21188b02aa42",
"utm_campaign": "120236311971890504",
"utm_medium": "cpc",
"utm_source": "fb"
}

Reading the footprint:

Attribution parameters:

  • affid: 1000 → Our Facebook channel identifier
  • aff_sub: fb → Confirms Facebook
  • campaign_id: 120236311971890504 → Specific campaign
  • ad_id: 120236311972130504 → Specific ad
  • adset_id: 120236311972120504 → Specific ad set
  • utm_source: fb, utm_medium: cpc → Paid Facebook traffic

Facebook parameters:

  • fbclid: IwZXh0bgNhZW0... → Facebook Click ID (full tracking)
  • _gl: 1*17a11gh... → Google Linker parameter (cross-domain tracking)
  • _gcl_au: MjExMDA... → Google Ads User ID

Payment/Product parameters:

  • content_ids: ["bundle-micro-ol-itirz-monthly-50off"] → Product purchased
  • content_type: product → It’s a product sale
  • currency: USD → US dollars
  • transaction_id: d9c26d5e... → Unique transaction

User identification:

  • email: user@example.com → PII (should probably be redacted in exports)
  • posthog_distinct_id: 019aa243... → PostHog user ID
  • posthog_session_id: 019aa243... → PostHog session ID

What this tells you:

  • Facebook campaign 120236311971890504, ad 120236311972130504 drove this sale
  • User purchased “bundle-micro-ol-itirz-monthly-50off” product
  • Sale tracked in transaction d9c26d5e-f19c-4f71-8712-21188b02aa42
  • Can attribute revenue to specific Facebook ad
  • Can connect to PostHog user profile for full journey

Insight discovered: content_ids parameter came from payment system

  • How do you know? Checked a few sales, this parameter appeared on all of them
  • Format is obvious: “bundle-micro-ol-itirz-monthly-50off” = product name
  • Now you know: content_ids = product purchased

The Critical Practice: Manual Event Checking

Section titled “The Critical Practice: Manual Event Checking”

Never Trust Charts Without Checking Raw Events

Section titled “Never Trust Charts Without Checking Raw Events”

Wrong workflow:

  1. Look at chart showing 100 conversions
  2. Trust the number
  3. Make decisions

Right workflow:

  1. Look at chart showing 100 conversions
  2. Export raw events that formed this number
  3. Check 5-10 events manually
  4. Understand: What parameters do they have? Are they consistent? Any anomalies?
  5. THEN trust the chart and make decisions

Without checking raw events:

  • You might be counting test transactions
  • Events might be missing key parameters
  • Duplicate events might inflate numbers
  • Attribution might be broken

With checking raw events:

  • You SEE that transaction IDs are unique
  • You SEE that all have proper attribution parameters
  • You SEE any test transactions (obvious from emails or IDs)
  • You KNOW the data is clean

Do this regularly:

  1. Go through your own funnel

    • Click your own ad
    • Land on landing page
    • Fill out forms
    • Complete purchase (use test mode)
  2. Watch events fire in real-time

    • Open PostHog/analytics tool
    • See events appear as you go through funnel
    • Check parameters on each event
  3. Verify the footprints

    • Landing event has UTM parameters? ✓
    • Lead event has email + UTM parameters? ✓
    • Purchase event has transaction_id + UTM parameters? ✓
    • All events linked by same user_id? ✓

Time required: 10-15 minutes

Value: Complete understanding of how data flows through your system


When you change something in your system, parameters change:

Example: Funnel Redesign

Before redesign (Old funnel):

{
"funnel_step": "landing",
"page_variant": "A",
"quiz_version": 1
}

After redesign (New funnel):

{
"funnel_version": "v2",
"landing_variant": "discount-01",
"quiz_enabled": true,
"quiz_questions": 5
}

What changed:

  • funnel_stepfunnel_version (renamed)
  • page_variantlanding_variant (renamed)
  • quiz_versionquiz_enabled + quiz_questions (split into two fields)

Why this matters: If you see events with old schema and new schema mixed, you know when the redesign launched.

Payment system integration:

  • Before: No transaction_id, no content_ids
  • After: transaction_id, content_ids, billing_address appear

New traffic source:

  • Before: Only utm_source + utm_medium
  • After: affid, aff_sub, tid appear for granular tracking

A/B test launch:

  • Before: Just landing page path
  • After: ab_test_variant, ab_test_id parameters appear

Mobile app tracking added:

  • Before: Only web events
  • After: platform: ios/android, app_version, device_id appear

Keep a changelog:

2025-11-01: Added content_ids parameter to purchase events (payment system integration)
2025-11-05: Changed quiz_version from number to boolean quiz_enabled
2025-11-10: Added affid parameter for all traffic sources
2025-11-15: Split landing_page into landing_page + landing_variant

Why: When analyzing historical data, you need to know when parameters changed


Step 1: Export raw events (1 minute)

  • Sales from yesterday
  • Or leads from last week
  • Or landing page views from specific campaign

Step 2: Open in spreadsheet (ugly data, accept it)

  • Columns: user_id, affid, utm_source, campaign_id, ad_id, parameters (JSON)
  • Looks messy - this is normal

Step 3: Scan for patterns (2-3 minutes)

  • Do all events have affid? (If not, tracking is broken)
  • Do all sales have transaction_id? (If not, payment tracking is broken)
  • Any test emails/IDs? (Filter these out)
  • Any unknown parameters? (Research them)

Step 4: Research unknown parameters (1 minute per parameter)

  • Copy parameter name + value
  • Paste into ChatGPT: “What is this parameter in web analytics: _gl=1*17a11gh…”
  • Get explanation
  • Document it for future reference

Step 5: Build understanding (ongoing)

  • Over time, you learn your schema intimately
  • You recognize patterns instantly
  • You spot anomalies immediately
  • You become a better hunter

Bad hunter:

  • Only looks at dashboards
  • Doesn’t understand raw data
  • Can’t explain where numbers come from
  • Trusts charts blindly
  • Lost when data looks wrong

Good hunter:

  • Checks raw events regularly
  • Understands every parameter
  • Can trace any number back to source
  • Verifies data manually
  • Spots issues immediately

Great hunter:

  • Knows schema evolution history
  • Recognizes patterns across systems
  • Can debug attribution issues
  • Understands how every system connects
  • Owns the data, doesn’t delegate to developers
  1. Look at raw data daily (not just dashboards)
  2. Research every unknown parameter (build vocabulary)
  3. Walk through funnel manually (see events fire)
  4. Track schema changes (document evolution)
  5. Check events before trusting charts (verify reality)
  6. Own the data (don’t treat it as “developer shit”)

This is not technical work. This is marketing analytics work.


  1. Raw data is ugly - accept it: IDs, affids, UTMs, JSON blobs are the core
  2. Charts are consequences: Everything flows from understanding raw parameters
  3. Never trust charts without checking raw events: 5-10 events is enough to verify
  4. Walk through your own funnel: See how events actually fire
  5. Research unknown parameters: ChatGPT/Google, takes 1 minute
  6. Track schema evolution: Changes tell the story of system changes
  7. Better hunter = better marketer: The more you understand parameters, the better your analysis
  8. This is your job, not developer’s: Owning raw data is core marketing analytics work

Being comfortable with ugly data is not optional. It’s fundamental.