Article
Stop the Drift: Validating GTM Event Specs with Claude Code
The tracking plan is the GTM equivalent of a peace treaty: frequently cited, occasionally updated, and technically non-binding. In most high-growth companies, the document defining what product data is being tracked lives in a spreadsheet. When an engineer changes trial_started to Trial_Started in the codebase, the tracking plan usually stays the same. The CRM, meanwhile, is expecting neither—it’s looking for trial_signup.
This is tracking plan drift. It is the primary reason why lifecycle triggers fail, why attribution reports look like Swiss cheese, and why RevOps leaders spend weekends in data-cleanup hell.
While enterprise tools like Avo or Segment Protocols govern these schemas, they require significant buy-in and budget. For a technically capable GTM operator, the goal is simpler: ensure that any event fired by the product team actually has a home in the downstream CRM before it reaches the production API. By using Claude Code to build a local, schema-aware validator, we can treat tracking governance as a code hygiene problem rather than a bureaucratic one.
The Cost of Silent Failures
Historically, many CDPs and CRMs handled unknown event properties gracefully by simply ignoring the unmapped data. That grace is disappearing.
HubSpot recently updated its Custom Behavioral Events API to return a strict HTTP 400 error when it receives an unknown event name. If your product fires an event that isn't pre-defined in HubSpot, the sync doesn't just skip that field; the entire call fails. Furthermore, HubSpot imposes a 50-character limit on internal property names.
Salesforce is equally opinionated. If you use Platform Events for real-time tracking, every API name must append the __e suffix. If your tracking plan calls for User_Login but your Salesforce Platform Event is User_Login__e, your integration will break.
When these mismatches occur, data is lost, and the revenue team loses trust in the system. To prevent this, we need to lint our tracking plans against our actual CRM metadata.
The Architecture: A Local GTM Linter
We can build a Python-based CLI tool that compares a standard Segment-style rules.json tracking plan against a simplified representation of a CRM schema.
Our validator will check for:
- Event Existence: Does the event in the tracking plan have a corresponding object or event type in the CRM?
- Naming Constraints: Are property names under the 50-character HubSpot limit?
- Type Mismatches: Is the tracking plan sending a string to a field defined as a boolean in the CRM?
- Casing Sensitivity: Do the naming conventions (snake_case vs. PascalCase) match the CRM’s API names?
Building the Validator with Claude Code
To start, initialize a project directory with two files: tracking_plan.json (the source of truth from the product team) and crm_schema.json (an export of your CRM’s custom event definitions).
Instead of writing the boilerplate ourselves, we use Claude Code to generate the validation logic based on the specific constraints of our tech stack.
The Prompt for Claude Code:
claude "Create a Python script `validate_gtm.py` that parses `tracking_plan.json` and compares it to `crm_schema.json`.
Strict rules to enforce:
1. HubSpot constraint: Property names must be <= 50 characters.
2. Salesforce constraint: If the CRM destination is SF, event names must end in '__e'.
3. Match property names exactly. If 'Signup Date' is in the plan but 'signup_date' is in the CRM, flag as a casing error.
4. Ensure data types match (e.g., plan 'number' vs CRM 'integer').
Output a summary of errors and return a non-zero exit code if failures are found."
The Implementation Detail
Claude Code produces a script that iterates through the rules.json (the format Segment uses for Protocols). A typical rules.json snippet looks like this:
{
"name": "Trial Started",
"rules": {
"properties": {
"context": { "type": "object" },
"properties": {
"plan_id": { "type": "string" },
"is_enterprise": { "type": "boolean" }
}
}
}
}
Your crm_schema.json is a representation of your CRM state, which you can generate via an API script or a manual export. It should look like this:
{
"events": [
{
"api_name": "Trial_Started__e",
"properties": {
"plan_id": "string",
"is_enterprise": "boolean"
}
}
]
}
As the script runs, it looks for the absence of matches. If the product team adds a referral_source property to the tracking plan but RevOps hasn't created that property in HubSpot, the script will catch it during the build phase—not after the data has already been dropped by the API.
Shifting GTM Governance Left
A script is only useful if it runs automatically. The best place for this validator is in a pre-commit hook or as a step in a CI/CD pipeline like GitHub Actions.
If your tracking plan lives in a Git repository, every pull request that modifies the tracking plan should trigger this script. If an engineer attempts to merge a new event that doesn't exist in the CRM, the build fails. This shifts the responsibility of GTM data integrity "left." Instead of RevOps catching errors in a weekly audit, the system prevents the error from ever entering production.
Trade-offs: Build vs. Buy
Large teams with enterprise budgets should likely buy governance platforms like Avo. They provide UI-based collaboration that a local CLI tool cannot replicate. However, for many teams, the overhead of a new vendor is the primary blocker to better data.
A local validator is free, lives where the code lives, and can be customized in minutes to account for specific edge cases—like legacy custom objects in Salesforce that refuse to follow naming conventions.
Static schema validation is not a silver bullet. It won't catch runtime anomalies, such as passing the string "undefined" into a field that expects an email address. For those, you still need monitoring and alerting on your ingestion pipeline.
The Move Toward GTM Engineering
Building your own linter is a practical step toward GTM engineering: applying software engineering principles to revenue systems. By treating your tracking plan as a data contract that must be validated against downstream systems, you move away from the "hope and pray" method of data sync. You catch HubSpot 400 errors before they happen, and you ensure your CRM remains a reliable source of truth for the people who depend on it.
— C.B.