pixellint

Analytics · engineers · PMs

track without an event name still 200s

A blank event name is how downstream destinations get an event called nothing, or drop it later. Segment accepted the HTTP. Your warehouse did not get a column they can group by.

writeKey may live in basic auth

POST api.segment.io/v1/batch (or a regional segmentapis.com host). writeKey identifies the source. Segment also accepts it as HTTP basic auth (username, empty password), so it is not required in the body. An empty writeKey in the body is still flagged: vendor.segment.body.writeKey.empty. A missing key with no basic auth is a source that never arrived, which you will debug as a silent destination.

A bare JSON body with no URL is told apart from PostHog by writeKey, userId, and anonymousId versus api_key and distinct_id. Copying a PostHog capture into Segment, or the reverse, is how both packs stay quiet and neither destination sees a person.

{
  "writeKey": "SOURCE_WRITE_KEY",
  "batch": [{
    "type": "track",
    "event": "Order Completed",
    "userId": "user-123",
    "timestamp": "2026-08-21T18:04:00Z",
    "properties": { "revenue": 19.99, "currency": "USD" }
  }]
}

batch type is a closed set

Each row in batch needs type: identify, track, page, screen, group, or alias. Pixellint: vendor.segment.body.type.missing and .invalid. A single call carries its type in the URL path (/v1/track, /v1/identify), so the type contract is scoped to the batch and stays quiet on a single call. Do not invent a seventh type named conversion because Meta uses that word.

track is a verb. identify is who. page is a view. screen is the mobile analog. Sending page as track('page') and also as page() is how you double-count SPA views. alias merges identities; doing it twice in opposite directions is how you glue the wrong people together.

track requires an event name

The track spec marks event as required. The collector still takes a call that omits it. It does not invent a name. Pixellint: vendor.segment.body.track_requires_an_event_name, scoped to type track. identify, page, screen, group, and alias do not need event. A mixed batch only flags the track rows. Downstream warehouses get a null column. Ads destinations get an unmapped event.

Name the event in one dictionary and keep it. Order Completed versus Purchase versus purchase is three destination mappings. The HTTP 200 will not tell you the Meta destination is looking for Purchase.

pixellint validate json @batch.json --rulepack vendor/segment

userId or anonymousId, ISO time

Every call needs userId or anonymousId. Pixellint: vendor.segment.body.call_needs_an_identifier. A track with properties and no identity is associated with nobody. Identify later does not retroactively attach a nameless event unless you sent anonymousId and then alias. Empty strings are not ids.

timestamp is ISO 8601. Leave it out for events happening now (Segment stamps received time). Send it for replays. An epoch number fails vendor.segment.body.timestamp.invalid. The same clock bug as PostHog, different pack. A warehouse sync that omits timestamp files a week of history as today.

Destinations are a second schema

A valid Segment payload can still fail Meta CAPI after a destination mapping hashes the wrong field, uses Date.now() for event_time, or drops event_id. Validate the destination payload too, not only the source call. The segment pack checks the HTTP Tracking API body. It does not inspect every destination's downstream schema. That is what vendor CAPI packs are for, on the body you actually POST to Meta, TikTok, or Pinterest.

PII fans out. A CDP makes it easy to send raw email to five vendors. Hash at the destination mapping or strip. Do not assume the CDP hashed it. Pixellint is not affiliated with Segment. Run pixellint validate json on the batch and again on the CAPI body the destination emitted.

Check the artifact

Paste the pixel URL or JSON body into the playground. Same engine as pixellint validate. Nothing leaves the tab.