Event Schema Validation
Why unvalidated events are a slow-motion data quality disaster
A tracking event with no enforced schema accepts whatever shape a developer happens to send — a price field that's sometimes a string, sometimes a number, occasionally missing entirely; an event_name typo (Purcahse instead of Purchase) that silently creates a brand-new, disconnected event type. None of this throws an error at collection time. It surfaces weeks later as a dashboard with inexplicably low purchase counts, or a dbt model crashing on a type it didn't expect — by which point the root cause is buried under thousands of already-collected malformed events.
JSON Schema as the enforcement mechanism
A JSON Schema document formally defines each event's required fields, allowed types, and allowed value sets (e.g. currency must be a 3-letter ISO code, event_name must be one of a fixed enumerated list). Validating every event against this schema at the point of collection — not downstream in the warehouse — means malformed events get rejected (or flagged) immediately, with an actionable error pointing at exactly which field was wrong, instead of silently polluting the warehouse.
{
"$schema": "http://json-schema.org/draft-07/schema#",
"title": "PurchaseEvent",
"type": "object",
"required": ["event_name", "transaction_id", "value", "currency", "items"],
"properties": {
"event_name": { "const": "purchase" },
"transaction_id": { "type": "string", "minLength": 1 },
"value": { "type": "number", "minimum": 0 },
"currency": { "type": "string", "pattern": "^[A-Z]{3}$" },
"items": {
"type": "array",
"minItems": 1,
"items": {
"type": "object",
"required": ["item_id", "price", "quantity"],
"properties": {
"item_id": { "type": "string" },
"price": { "type": "number", "minimum": 0 },
"quantity": { "type": "integer", "minimum": 1 }
}
}
}
},
"additionalProperties": false
}additionalProperties: false catches far more bugs than it seems like it should
item_id but types itemId gets silently accepted as an 'extra field' rather than rejected as 'missing required field itemId.' Setting additionalProperties: false forces every field name to be deliberate and schema-known, turning what would be a silent, permanent data gap into an immediate, loud validation failure during development.Where validation should actually run
Validating in the browser/app before sending catches bugs earliest but can't be fully trusted (client code can be bypassed or tampered with). Validating at a server-side ingestion point (a Tag Manager server container's client, a custom ingestion API, or a dedicated event-validation service sitting in front of your warehouse load path) is the layer that actually matters for data integrity, since it's the last point you control before data becomes permanent. Many teams run both: client-side validation for fast developer feedback during implementation, server-side validation as the non-negotiable gate before anything reaches storage.
Schema versioning and evolution
Event schemas change as products evolve — a new required field, a new allowed event_name. Version the schema itself (purchase_event.v2.schema.json) rather than silently mutating it in place, so historical events validated against v1 remain understood as v1-shaped, and consumers (including your own dbt staging models) can explicitly branch behavior per schema version rather than assuming every row in a table conforms to whatever the schema currently says.
What's Next?
Schema validation catches malformed events at the door — the complementary discipline is continuously monitoring the data that does get through, for quality problems a schema alone can't express, like sudden volume drops or unexpected distribution shifts.
I build these systems professionally.
Whether it's a RAG pipeline, analytics migration, or AI workflow — let's talk.