Snowplow validates every self-describing event and entity against a JSON Schema registered in an Iglu schema registry, keyed by its exact schema URI - a validation error almost always means the payload does not match the registered schema's required fields, types, or additionalProperties constraint, or that the schema version in your tracking code does not match what is actually registered.
Why this happens
Unlike Mixpanel or GA4, where a mistyped or extra property is silently accepted, Snowplow treats data quality as a pipeline-level guarantee: every self-describing event is checked against its declared JSON Schema before entering the enriched "good" stream, and anything that fails is routed to a separate "bad rows" stream instead of being dropped silently or accepted anyway.
That is a deliberate design tradeoff - strict correctness over permissiveness - but it means schema mismatches other tools would ignore become hard failures here: a schema bumped from 1-0-0 to 1-0-1 in the registry without updating the version string your tracker sends, a required field left null, or an extra property when additionalProperties is false will all fail validation.
Fix it
- Locate the exact bad row in your pipeline's bad stream (or Snowplow Micro during local development) and read its validationError field - it names the specific field and constraint that failed, not just "invalid".
- Confirm the schema URI and version your tracker references (iglu:vendor/name/jsonschema/1-0-0 style) exactly matches a schema actually registered in your Iglu repository - a version bump on one side without the other is the single most common cause.
- Use Snowplow Micro locally during development - it runs the same validation logic as production and gives immediate, readable feedback before you deploy to a real collector.
- Check whether the schema was authored with additionalProperties set to false - if so, every property you send must be explicitly declared, including ones that seem harmless to add ad hoc.
- For required-field failures, confirm your tracking code always populates that field even in edge-case flows - a schema that assumes a field is always present will reject events from any code path that skips it.
How to verify it worked
Run the same event through Snowplow Micro (or a local pipeline) after your fix and confirm it appears in the "good" event count with zero new bad rows; inspect the enriched event's fields to confirm the values map exactly as intended, not just that validation passed.