vendor/posthog · vendor documented
PostHog capture treats an epoch timestamp as now
You backfill six months of events. Insights show a spike today. The events are not missing. They are stamped with the time PostHog received them.
timestamp is ISO 8601. An epoch number is a different type. Capture still accepts the request. The historical value is not applied. Missing event or distinct_id is not ingested, and PostHog still returns 200 OK. distinct_id is at most 200 characters. properties.distinct_id is accepted when the top-level field is absent. $create_alias needs properties.alias. $groupidentify needs $group_type and $group_key.
timestamp is ISO 8601, not epoch
Send 2026-07-26T06:00:00Z. Segment uses the same shape. Amplitude wants milliseconds. If your pipeline already emits epoch for Amplitude, do not reuse the field on PostHog.
Every event needs distinct_id
On a single capture and on every row of batch. PostHog's own docs: a missing name, a missing distinct_id, or an empty distinct_id is not ingested, and you still get 200.
What this pack matches
Rules
Codes are stable. A finding in CI, MCP, or the playground lands on the same id.
| Field | Required | What it checks | Rule ids | Source |
|---|---|---|---|---|
api_key |
required | It is the project API key, and PostHog rejects the request without it. Fix: Send the project API key from your PostHog project settings. | vendor.posthog.body.api_key.missingvendor.posthog.body.api_key.empty |
docs |
historical_migration |
optional | PostHog documents `historical_migration` as a boolean on `/batch`. Set it to `true` when backfilling so events are processed in order. | vendor.posthog.body.historical_migration.emptyvendor.posthog.body.historical_migration.invalid |
docs |
event |
required | It is the name of the event being captured. Fix: Name the event, such as `user signed up`. | vendor.posthog.body.event.missingvendor.posthog.body.event.empty |
docs |
distinct_id |
optional | It identifies the person the event belongs to. PostHog documents at most 200 characters, and also reads `properties.distinct_id` when the top-level field is absent. Fix: Send `distinct_id` at the top level, at most 200 characters. | vendor.posthog.body.distinct_id.emptyvendor.posthog.body.distinct_id.invalid |
docs |
properties.distinct_id |
optional | PostHog reads `properties.distinct_id` when the top-level `distinct_id` is absent. Prefer the top-level field. Fix: Send `distinct_id` at the top level, or inside `properties` when the client cannot. | vendor.posthog.body.properties.distinct_id.emptyvendor.posthog.body.properties.distinct_id.invalid |
docs |
timestamp |
optional | PostHog documents the timestamp as ISO 8601. An epoch number is read as the ingestion time instead. Fix: Send an ISO 8601 timestamp, such as `2026-07-26T06:00:00Z`. | vendor.posthog.body.timestamp.emptyvendor.posthog.body.timestamp.invalid |
docs |
properties.$ip |
optional | It is the visitor IP, sent unhashed. | vendor.posthog.body.properties.$ip.empty |
docs |
properties.$process_person_profile |
optional | PostHog documents this as a boolean. Set it to `false` to capture an anonymous event. | vendor.posthog.body.properties.$process_person_profile.emptyvendor.posthog.body.properties.$process_person_profile.invalid |
docs |
properties.alias |
optional | It is the distinct id being merged. PostHog documents it as required on `$create_alias`. Fix: Set `properties.alias` to the distinct id this person should merge into. | vendor.posthog.body.properties.alias.empty |
docs |
properties.$group_type |
optional | It is the group type on `$groupidentify`. PostHog documents at most 400 characters. | vendor.posthog.body.properties.$group_type.emptyvendor.posthog.body.properties.$group_type.invalid |
docs |
properties.$group_key |
optional | It is the group key on `$groupidentify`. PostHog documents at most 400 characters. | vendor.posthog.body.properties.$group_key.emptyvendor.posthog.body.properties.$group_key.invalid |
docs |
properties.$current_url |
optional | It is the page URL or path on `$pageview`. PostHog documents it on the capture pageview example, including a path-only value. Fix: Send `properties.$current_url` as the page path or full URL, or drop the empty pair. | vendor.posthog.body.properties.$current_url.empty |
docs |
properties.$session_id |
optional | It is the session id. PostHog documents it on the `$pageview` capture example. Fix: Send `properties.$session_id`, or drop the empty pair. | vendor.posthog.body.properties.$session_id.empty |
docs |
properties.$screen_name |
optional | It is the screen name on `$screen`. PostHog documents it on the capture screen-view example. Fix: Send `properties.$screen_name`, or drop the empty pair. | vendor.posthog.body.properties.$screen_name.empty |
docs |
properties.$survey_id |
optional | It identifies the survey. PostHog documents it as required on `survey sent`, `survey shown`, and `survey dismissed`. Fix: Set `properties.$survey_id` to the survey id from PostHog. | vendor.posthog.body.properties.$survey_id.empty |
docs |
body.event_needs_an_identifier |
required | The event carries no `distinct_id`. PostHog documents it as required at the top level, and also reads `properties.distinct_id` when the top-level field is absent. A missing or empty id is not ingested, and the endpoint still returns 200. Fix: Send `distinct_id` at the top level. | vendor.posthog.body.event_needs_an_identifier |
docs |
body.hashed_plaintext_field |
required | `properties.$ip` looks like a SHA-256 digest, but PostHog documents it as an IP address. Fix: Send the raw IP address. | vendor.posthog.body.hashed_plaintext_field |
docs |
body.alias_requires_alias |
required | A `$create_alias` event carries no `properties.alias`. PostHog documents that field as the distinct id being merged. Fix: Set `properties.alias` to the distinct id this person should merge into. | vendor.posthog.body.alias_requires_alias |
docs |
body.groupidentify_requires_type_and_key |
required | A `$groupidentify` event is missing `$group_type` or `$group_key`. PostHog documents both, each at most 400 characters. Fix: Set `properties.$group_type` and `properties.$group_key`. | vendor.posthog.body.groupidentify_requires_type_and_key |
docs |
body.survey_requires_survey_id |
required | A survey event carries no `properties.$survey_id`. PostHog documents that field as required on `survey sent`, `survey shown`, and `survey dismissed`. Fix: Set `properties.$survey_id` to the survey id from PostHog. | vendor.posthog.body.survey_requires_survey_id |
docs |
Validate a payload
pixellint validate json @payload.json --rulepack vendor/posthog
Try this failing payload in the playground. PostHog capture with a hashed $ip.
{"api_key":"phc_abc123","event":"user signed up","distinct_id":"user-10024","timestamp":"2026-07-26T06:00:00Z","properties":{"$ip":"a85e9ca18f34935ab9b0381b25bfad2455444112b0149270fd88e3da172fe196"}}
cargo install pixellint
·
npm install pixellint