Skip to main content

CDP Tracking Plan

A tracking plan is the contract between the apps that send you events and the CDP that stores them. You declare which event names exist and what their properties should look like, and every inbound payload is checked against that contract at ingest time. This guide covers declaring events, choosing between soft and strict enforcement, reading the violations feed, and keeping the plan under version control. The segments and scores documented in CDP audiences are only as trustworthy as the events they filter over — a tracking plan is how you keep that input honest. The same surface backs the dashboard at Integrations → CDP → Tracking Plan. Everything below also works from the API, so your CI can drive it.

1. What a tracking plan is

A tracking plan row declares one event:
  • event_name — the event string your SDK sends (case-sensitive). One row per name; re-posting the same name updates it.
  • properties_schema — a subset of JSON Schema: properties maps each property name to a rule (type and optionally an enum value set), and required lists properties that must be present. Types supported: string, number, integer, boolean, object, array.
  • enforcementsoft (log violations, accept the event) or strict (reject violating events with HTTP 422).
At ingest time every track, page, screen, and batch leg whose event name matches a plan row is validated. Mismatches are recorded in the violations feed whether or not the event was rejected, so soft mode and strict mode share one audit trail. Validation rules, in the order they fire:
  • A property listed in the plan’s required array that arrives missing or null → missing_required.
  • A property whose value’s type differs from the rule’s typetype_mismatch (integer values satisfy a number rule).
  • A property whose value falls outside the rule’s enum set → enum_mismatch.
  • A property present in the payload without a rule in the schema → extra_property (only flagged once the schema declares at least one property).
  • An event name with no plan row at all → unknown_event, recorded as soft/accepted so SDK drift shows up without costing you events.

2. Prerequisites

  • Access. An API key for your workspace, or a dashboard session with the owner, admin, or developer role. Analyst and marketer roles cannot edit the plan — a strict-mode flip changes whether customer events are accepted, so the write side is restricted.
  • An events list. The event names you want to govern. Start with the two or three that drive critical segments or flows, not the whole catalog.
  • A naming convention for event_name. It must match the event string in your SDK exactly, including case.

3. Declare an event

POST /api/v1/cdp/tracking-plan upserts a plan row, so the same call creates and later updates a row:
Response (fields marked * only included when you sent them — description defaults to null):
Notes:
  • Required properties can come from properties_schema.required, from a top-level required_properties array, or both — the server merges, deduplicates, and stores them sorted.
  • New events almost always start in soft mode. Watch the violations feed for a few days, then flip to strict once the producers are clean.
  • Schema fields beyond properties, required, and the governance classification block are stored but ignored by the validator, so you can paste a fuller JSON Schema and get forward-compat storage.

4. List, update, and delete

List the catalog (keyset-paginated — walk pagination.cursor):
Update a row by re-posting its event_name — the request above with "enforcement": "strict" flips enforcement in place. Delete stops validation for that event name (historical violations are kept):

5. Soft vs strict enforcement

The mode decides what happens when a violation fires on a matched plan row:
  • Soft — the event is accepted and stored as usual, and the violation is recorded for you to review. Producers drift without losing data.
  • Strict — the event is rejected with HTTP 422 and the violation is recorded with was_rejected: true. Nothing untyped enters cdp_events, so segments and models stay pure.
Two operational rules:
  1. Ship new events in soft. The feed tells you whether your SDK matches what you declared before you arm the gate.
  2. Flip to strict only for events whose producers you control end-to-end. A customer-side SDK on a mobile version you cannot force-update will 422 real purchases if the plan and SDK disagree.
Unmatched event names (no plan row) are always accepted — strictness only applies to events you have declared. If a strongly-governed workspace wants to reject undeclared events wholesale, that’s a policy to raise with Support rather than a per-event setting.

6. Reading the violations feed

GET /api/v1/cdp/tracking-plan/violations is newest-first and filterable by event_name and enforcement, with a cursor for paging:
Each row gives you the failing path, the expected shape, and what actually arrived — enough to pinpoint the producer. was_rejected tells you whether the event was dropped (strict) or stored anyway (soft). In the dashboard, the Violations tab on the Tracking Plan page shows the same stream with event-name and enforcement filters.

7. Plan-as-code in CI/CD

Treat the plan like any other infrastructure — a JSON file in your repo, applied on deploy:
Because POST is an upsert, running the script twice is a no-op when nothing changed. A common pipeline: authors edit the JSON in a pull request, CI posts it on merge, and the violations feed confirms the schema and producers agree before anyone flips enforcement to strict. Keep curl exit codes checked so a 422 on the plan itself fails the deploy.

8. Troubleshooting

Events rejected with 422 in strict mode, unexpectedly. A producer is sending a payload that diverges from the plan. Open the violations feed filtered to that event — the violation_path and actual fields name the offending property. Either fix the SDK payload, or widen the schema (add the property rule, extend the enum) if the new shape is legitimate. If you cannot fix the producer immediately, flip the event back to soft to stop the data loss while you triage. missing_required across every event of a type. Usually a producer version that stopped sending the property (a refactor renamed it) or sent it as null. Compare the actual payloads; the fix is on the producer side. extra_property noise. Your SDK sends something the plan does not declare. Add a rule for it — optional properties only need a type entry under properties. Schema evolution. Adding new optional properties to a payload is always safe. Removing a property, narrowing a type, or changing a value set are breaking changes: coordinate the plan edit with the producer rollout, and under soft enforcement watch the feed while both versions are live. unknown_event entries. An event arrived that no row governs. Either declare a row for it, or fix the caller’s event-name casing/spelling — the match is case-sensitive.

See also