Togo checks each event when it arrives and reports what is wrong with it as a diagnostic: a code, a severity, what it means, and the fix. Live View shows them on each event, and the verify_tracking MCP tool returns them to your coding agent with the same text you see here.
An error means production drops or loses the data: the event was refused when it arrived, or a part of it will not be stored. A warning means production keeps the event as sent, but it is not what its event definition says, or it is very likely a bug.
Events refused when they arrive are also named in the ingest response, and the SDKs log each one as a [togo] error, so the console or your server log is the first place to look:
{ "accepted": 19, "rejected": [{ "eventId": "…", "reason": "timestamp_too_old" }] }
A code is permanent: its meaning and its severity never change.
| Code | Severity | What it is |
|---|---|---|
timestamp_too_far_future |
error | Dated in the future |
timestamp_too_old |
error | Older than 7 days |
envelope_property_value_too_long |
error | A property value is over 1,024 characters |
envelope_too_many_properties |
error | More than 256 properties |
session_id_invalid |
error | The session id is not valid |
event_name_reserved |
error | The name starts with $ |
identity_missing |
error | No userId or anonymousId |
event_rejected |
error | Refused when it arrived |
event_not_defined |
warning | The event has no definition |
event_not_defined_dropped |
error | Dropped: the event has no definition |
property_not_defined |
warning | A property is not in the definition |
required_property_missing |
warning | A required property is missing |
invalid_property_type |
warning | A property has the wrong type |
invalid_enum_value |
warning | A value is not one of the allowed values |
property_value_too_long |
warning | A value is longer than its definition allows |
event_deprecated |
warning | The event is deprecated |
property_deprecated |
warning | A property is deprecated |
event_name_near_duplicate |
warning | Looks like an existing event |
event_name_contains_email |
error | The name contains an email address |
event_name_too_long |
error | The name is over 64 characters |
event_name_cap_reached |
error | The project has 1,000 event names |
property_pii_dropped |
error | Dropped: contains an email address |
property_contains_email |
warning | Contains an email address |
session_id_contains_email |
warning | The session id is an email address |
user_id_contains_email |
warning | The userId is an email address |
identify_refused |
error | identify() would be refused |
person_property_not_defined |
error | Dropped: the profile property has no definition |
person_property_key_invalid |
error | Dropped: the profile property name cannot be defined |
person_property_invalid_value |
error | Dropped: the profile value does not fit its definition |
person_property_set_once_null |
error | Dropped: setOnce with no value |
event_id_reused |
error | Two different events share an eventId |
clock_skew_large |
warning | The sending clock is more than an hour off |
live_key_from_localhost |
warning | A local build is sending with the live key |
timestamp_too_far_future
Error — production drops or loses the data. Dated in the future.
The event is dated more than 5 minutes after Togo received it, even after correcting for the device clock. It was refused and not stored. The SDKs stamp events themselves, so this nearly always comes from a client that sets its own timestamp.
Fix, for your coding agent: Find where the code that sends the event named in event sets its timestamp. Send the time the event happened, in UTC ISO-8601 with a Z suffix, or leave the timestamp out so the SDK stamps it. Never send a time computed in a local time zone or a time in the future. Then call verify_tracking again.
timestamp_too_old
Error — production drops or loses the data. Older than 7 days.
The event happened more than 7 days before Togo received it. It was refused and not stored. Usually a device that was offline for over a week, or old data sent through the live endpoint as a backfill.
Fix, for your coding agent: Check the timestamp the code sends with the event named in event. If it is a fixed or default date, remove it and let the SDK stamp the event when it happens. If this is a backfill of historical data, stop: the ingest endpoint accepts events up to 7 days old and nothing older. Then call verify_tracking again.
envelope_property_value_too_long
Error — production drops or loses the data. A property value is over 1,024 characters.
A string property value is longer than 1,024 characters, the platform ceiling. The event was refused and not stored. Long values are nearly always text a person typed, which does not belong in an event.
Fix, for your coding agent: Find the track() call for the event named in event and the string property that can exceed 1,024 characters. Remove it, or replace it with a short category or a length bucket. Never send text a person typed as an event property. Then call verify_tracking again.
envelope_too_many_properties
Error — production drops or loses the data. More than 256 properties.
The event carries more than 256 properties, the platform ceiling. It was refused and not stored.
Fix, for your coding agent: Find the track() call for the event named in event. It is probably spreading a whole object into the properties. Send only the properties its event definition lists, and move facts about the person to setPersonProperties(). Split an event that describes several things into one event per thing. Then call verify_tracking again.
session_id_invalid
Error — production drops or loses the data. The session id is not valid.
A custom session id is not 1–128 characters of letters, digits, _, ., :, @, +, % or -. The event was refused and not stored.
Fix, for your coding agent: Find where the code passes a custom sessionId to the SDK. Pass an opaque id from your own session store, 1–128 characters of letters, digits, _, ., :, @, +, % or - — no spaces or slashes. If the app has no session store of its own, stop passing sessionId and let the SDK manage sessions. Then call verify_tracking again.
event_name_reserved
Error — production drops or loses the data. The name starts with $.
Event names starting with $ are reserved for Togo. The event was refused and not stored. This is usually another analytics tool’s automatic events — PostHog’s $pageview, for example — being forwarded to Togo.
Fix, for your coding agent: Find what sends the event named in event. If another analytics tool forwards its automatic events to Togo, stop the forwarding and call Togo directly. If the code chose the name, rename it in lowercase snake_case without the $, and write its event definition with update_event_definitions before sending it. Then call verify_tracking again.
identity_missing
Error — production drops or loses the data. No userId or anonymousId.
The event has neither a userId nor an anonymousId, so no person can be credited with it. It was refused and not stored. The browser SDK always sends an anonymousId and the server SDK drops such an event itself, so this comes from other clients.
Fix, for your coding agent: Find the code that sends the event named in event. If it calls Togo over HTTP directly, use the Togo SDK for that runtime instead. If it uses @togohq/sdk-node, pass the signed-in person’s userId, or the anonymousId the browser sent, on every track() call. Then call verify_tracking again.
event_rejected
Error — production drops or loses the data. Refused when it arrived.
The event was refused when it arrived and was not stored, for a reason this view does not describe yet. The event’s rejection code names the reason.
Fix, for your coding agent: Read the rejection code on the event named in event — the ingest response carries it too, and the SDK logs it to the console — and look it up on the event diagnostics page, which lists every code with its fix. Change the call that sends the event to match. Then call verify_tracking again.
event_not_defined
Warning — production keeps the data as sent. The event has no definition.
No event definition covers this event. It is kept and can be queried, but it has no description, no property types and nothing to check it against, so the analyst knows only its name.
Fix, for your coding agent: Write an event definition for the event named in event with update_event_definitions: what it means, when it fires, when it does not, and each property it carries with its type. Use the name exactly as the code sends it. Do not rename the event in the code to match an existing definition unless they mean the same thing. Then call verify_tracking again.
event_not_defined_dropped
Error — production drops or loses the data. Dropped: the event has no definition.
This project keeps only defined events (strict event governance), and no event definition covers this one. Production drops it, and turning the setting off later does not recover it.
Fix, for your coding agent: Write an event definition for the event named in event with update_event_definitions before the code sends it again: what it means, when it fires, when it does not, and each property with its type. If the event should not exist, remove the track() call instead. Never turn strict event governance off without asking the person. Then call verify_tracking again.
property_not_defined
Warning — production keeps the data as sent. A property is not in the definition.
The event has a definition, and the property named in the diagnostic is not in it. The value is kept as sent, with no type or description.
Fix, for your coding agent: Compare the track() call for the event named in event with its event definition (get_event). If the property named in property is a typo for a defined one, fix the key in the code. If it is new and meant, add it to the definition with update_event_definitions, with its type and description. Then call verify_tracking again.
required_property_missing
Warning — production keeps the data as sent. A required property is missing.
The event definition marks the property named in the diagnostic as required, and the event did not carry it. The event is kept; charts that filter or group by that property miss it.
Fix, for your coding agent: Find the track() call for the event named in event and send the property named in property on every call, including every code path that fires the event. Check the definition with get_event for its type. Do not remove required from the definition unless the person confirms the property is optional. Then call verify_tracking again.
invalid_property_type
Warning — production keeps the data as sent. A property has the wrong type.
The property named in the diagnostic was sent with a different type than its definition says — a number as a string, for example, or a timestamp that is not UTC. The value is kept as sent, so charts that use it as the defined type get it wrong.
Fix, for your coding agent: The property named in property was sent as actualType, but its definition expects expected. Find the track() call for the event named in event and send the value as expected; a timestamp is UTC ISO-8601 with a Z suffix. Do not change the definition to match the code unless the person confirms the definition is wrong. Then call verify_tracking again.
invalid_enum_value
Warning — production keeps the data as sent. A value is not one of the allowed values.
The property named in the diagnostic has a fixed list of allowed values in its definition, and the event carried a value outside it. The value is kept as sent, and shows as its own category in every chart.
Fix, for your coding agent: Find the track() call for the event named in event and send only the allowed values listed in expected for the property named in property — check capitalisation and spelling. If the value is a real new case, ask the person before adding it to the definition with update_event_definitions. Then call verify_tracking again.
property_value_too_long
Warning — production keeps the data as sent. A value is longer than its definition allows.
A string value is longer than the maximum length its property definition sets. The value is kept as sent.
Fix, for your coding agent: Find the track() call for the event named in event and shorten the value of the property named in property to the length its definition allows — send a category or a length bucket instead of text. Never send text a person typed as an event property. Do not raise the definition’s limit unless the person asks. Then call verify_tracking again.
event_deprecated
Warning — production keeps the data as sent. The event is deprecated.
The event definition is marked deprecated. The event is still kept, but its definition says to send another event instead.
Fix, for your coding agent: Replace the track() call for the event named in event with the event named in expected, and carry its properties over as that event’s definition lists them (get_event). Remove the old call once the new one is in place; do not send both. If expected is empty, ask the person what replaces it. Then call verify_tracking again.
property_deprecated
Warning — production keeps the data as sent. A property is deprecated.
The property named in the diagnostic is marked deprecated in the event definition. Its value is still kept, but the definition says to stop sending it.
Fix, for your coding agent: Find the track() call for the event named in event and stop sending the property named in property. Read the event definition with get_event: if another property replaces it, send that one instead. Do not remove the property from the definition; a deprecated property stays defined so older builds keep working. Then call verify_tracking again.
event_name_near_duplicate
Warning — production keeps the data as sent. Looks like an existing event.
This name is new, and the key it gets looks like an existing event’s key — Sign up beside sign_up, for example. It is kept as a separate event, so the two are counted apart until someone merges them.
Fix, for your coding agent: The event named in event looks like the existing event key in expected. If they mean the same thing, change the code to send the existing event’s name exactly — list_events shows it. If they are different events, write a definition for the new one with update_event_definitions that says how it differs. Then call verify_tracking again.
event_name_contains_email
Error — production drops or loses the data. The name contains an email address.
The event name looks like it contains an email address — usually a value used as a name. The event is stored but can never be queried by name, and the name is not recorded.
Fix, for your coding agent: Find the track() call that builds the event name named in event from data. An event name is a fixed string in lowercase snake_case that says what happened, such as invite_sent. Move any per-person value out of the name, and never send an email address in an event at all. Then call verify_tracking again.
event_name_too_long
Error — production drops or loses the data. The name is over 64 characters.
The event name is longer than 64 characters, the longest a defined name can be — usually a value used as a name. The event is stored but can never be queried by name, and the name is not recorded.
Fix, for your coding agent: Find the track() call that sends the event named in event. An event name is a short fixed string in lowercase snake_case that says what happened, such as report_exported. If the name is built from data, use one fixed name and send the varying part as a property. Then call verify_tracking again.
event_name_cap_reached
Error — production drops or loses the data. The project has 1,000 event names.
The project already has 1,000 event names, so this new one is not recorded. The event is stored but cannot be queried by name. A project reaching the cap is nearly always building names from data.
Fix, for your coding agent: Find the code that builds event names from data — a page path, an id or a value inside the name. Use one fixed event name and send the varying part as a property instead, for example page_viewed with path. Ask the person to merge or remove the generated names in the dashboard’s Event registry. Then call verify_tracking again.
property_pii_dropped
Error — production drops or loses the data. Dropped: contains an email address.
This project has the email filter on, and the property named in the diagnostic has an email address in its key or value. Production drops that property and keeps the rest of the event.
Fix, for your coding agent: Find the track() call for the event named in event and remove the property named in property. An email address belongs on the person, set with identify() or setPersonProperties(), never on an event. If you need to know whether one was given, send a boolean instead. Then call verify_tracking again.
property_contains_email
Warning — production keeps the data as sent. Contains an email address.
The property named in the diagnostic has an email address in its key or value. The email filter is off for this project, so it is stored on the event row. Event properties are for booleans, counts, categories and lengths — never personal data.
Fix, for your coding agent: Find the track() call for the event named in event and remove the property named in property. An email address belongs on the person, set with identify() or setPersonProperties(), never on an event. If you need to know whether one was given, send a boolean instead. Then call verify_tracking again.
session_id_contains_email
Warning — production keeps the data as sent. The session id is an email address.
The custom session id sent with this test event contains an email address. A session id is stored on every event of the session, so the address would be stored in every one of them.
Fix, for your coding agent: Find where the code passes a custom sessionId to the SDK and pass an opaque id from your own session store instead — a random value, never the signed-in person’s email address. If the app has no session store of its own, stop passing sessionId and let the SDK manage sessions. Then call verify_tracking again.
user_id_contains_email
Warning — production keeps the data as sent. The userId is an email address.
The userId sent with the event contains an email address. A userId is stored on every event the person sends, so the address is stored in every one of them.
Fix, for your coding agent: Find where the code calls identify() or passes userId, and pass the app’s own stable account id — the database primary key — instead of the email address. Set the email address on the person with setPersonProperties() if the person wants it in Togo. Then call verify_tracking again.
identify_refused
Error — production drops or loses the data. identify() would be refused.
This device’s person already belongs to a different userId, so the call is refused rather than merging two people. Production keeps the events but does not link this userId.
Fix, for your coding agent: Two different users signed in on the same device without a sign-out between them. Find the app’s sign-out code and call the SDK’s reset() there, so the next sign-in starts a new person. Then sign out, sign in as a different user and call verify_tracking with expectIdentify again.
person_property_not_defined
Error — production drops or loses the data. Dropped: the profile property has no definition.
A profile property sent with setPersonProperties() has no definition. Production drops it and keeps the others.
Fix, for your coding agent: Define the person property named in property with update_person_property, giving its type and what it means, and then send it again; values sent before it was defined are not recovered. If it was a typo for an existing person property, list_person_properties shows the defined names; fix the key in the code. Then call verify_tracking again.
person_property_key_invalid
Error — production drops or loses the data. Dropped: the profile property name cannot be defined.
A profile property sent with setPersonProperties() has a name that is not lowercase snake_case, so it can never be defined. Production drops it and keeps the others.
Fix, for your coding agent: Find the setPersonProperties() call that sends the key named in property and rename it in the code to the name in expected — a person property name is lowercase snake_case, at most 64 characters. Then, unless list_person_properties already lists that name (email and name always are), define it with update_person_property, giving its type and what it means. Do not try to define the old name. Then call verify_tracking again.
person_property_invalid_value
Error — production drops or loses the data. Dropped: the profile value does not fit its definition.
A profile property was sent with a value its definition refuses — the wrong type, a value outside its allowed list, or one that is too long. Production drops it and keeps the others.
Fix, for your coding agent: Find the setPersonProperties() call that sends the person property named in property, and send a value that fits its definition: list_person_properties shows its type and allowed values. Do not change the definition to fit the code unless the person confirms the definition is wrong. Then call verify_tracking again.
person_property_set_once_null
Error — production drops or loses the data. Dropped: setOnce with no value.
A profile property was sent with setOnce and no value. There is nothing to set, so production drops it.
Fix, for your coding agent: Find the setPersonProperties() call that sends the person property named in property with setOnce. Only call it when there is a value — check for null or undefined first — or use unset if the intent was to clear the property. Then call verify_tracking again.
event_id_reused
Error — production drops or loses the data. Two different events share an eventId.
Two events with different contents arrived with the same eventId. Togo keeps one event per eventId — a retry is meant to reuse it — so production keeps only one of them.
Fix, for your coding agent: Find the code that sets eventId for the event named in event. Leave eventId to the SDK, or generate a new random id for every distinct event and reuse it only when retrying the same event. Never derive it from a name, a timestamp or a counter that can repeat. Then call verify_tracking again.
clock_skew_large
Warning — production keeps the data as sent. The sending clock is more than an hour off.
The time the client said it sent the batch is more than an hour away from when Togo received it. Togo corrects for it, so the event is kept at the right time, but from a server or your own client it usually means timestamps are being set wrongly.
Fix, for your coding agent: Find the code that sends the event named in event. If it calls Togo over HTTP directly, switch to the Togo SDK for that runtime, which sets both timestamps itself. If it uses an SDK, check it does not pass a timestamp of its own, and that the server’s clock is synchronised. Then call verify_tracking again.
live_key_from_localhost
Warning — production keeps the data as sent. A local build is sending with the live key.
The event was sent with the live write key from a page on localhost or a local address. It is counted as production traffic, so development clicks appear in the real numbers.
Fix, for your coding agent: Use the write-key pair’s test key, starting togo_pkt_, in development and the live key, starting togo_pkl_, only in the production build. Read the key from the build’s environment rather than hard-coding it, and check which value the local environment file sets. Then call verify_tracking again.
HTTP errors
These refuse the whole request rather than one event, so they come back as the response status, not as a diagnostic.
| Status | error |
Means | Retry? |
|---|---|---|---|
400 |
invalid_json |
The body is not JSON | No — fix the client |
400 |
invalid_batch |
The batch does not have the expected shape; issues says how |
No — fix the client |
401 |
missing_write_key |
No write key was sent | No |
401 |
invalid_write_key |
The key is not one of Togo’s, or it was revoked | No — see The write key was rejected |
413 |
batch_too_large |
The batch is over 1 MB | No — send smaller batches |
429 |
rate_limited |
The project sent too much too fast | Yes, after Retry-After |
503 |
enqueue_failed |
Togo could not accept the batch right now | Yes, with the same event ids |
503 |
inspect_unavailable |
A test-key batch could not reach the test views | Yes, with the same event ids |
The SDKs retry 429 and 5xx responses for you, keeping each event’s id so a retried event is recognised as the same event, and never retry a 4xx that would fail the same way again. A 503 never means the events were kept: retry them.