DEV Community

Aleksander Sekowski
Aleksander Sekowski

Posted on

A Segment Track Call Needs an Event Name. The HTTP 200 Does Not Check.

A server posts a track to api.segment.io/v1/batch. The response is 200. Downstream destinations never see the event.

Segment's HTTP API is a collector. It is not a schema checker. The track spec marks event as required. The collector still takes a call that omits it. It does not invent a name.

{
  "writeKey": "seg-write-key-abc",
  "batch": [
    { "type": "track", "userId": "019mr8mf4r" }
  ]
}
Enter fullscreen mode Exit fullscreen mode

No event. Nothing for a destination to key off. The 200 is "we received bytes."

I maintain Pixellint, an open source linter for pixels and tracking payloads. The Segment pack (vendor/segment) contracts the HTTP Tracking API, single and batched, against Segment's published spec. This is the finding:

$ pixellint validate json @batch.json
rulepack: vendor/segment (vendor: segment)
  error   vendor.segment.body.track_requires_an_event_name
          A `track` call carries no `event`. Segment documents the event
          name as required, and drops the call without it.
    fix:  Add the event name to the call.
    docs: https://segment.com/docs/connections/spec/track/
Enter fullscreen mode Exit fullscreen mode

identify, page, screen, group, and alias do not need event. The rule is scoped to type: track. A batch that mixes call types only flags the track rows.

On a single call, the type lives in the URL path (/v1/track) rather than the body, so the type enum is scoped to batch and stays quiet on a lone payload. The event-name check still applies where the body is a batch of tracks.

Every call needs a person

Segment documents that every call needs userId or anonymousId. A track with properties and a timestamp and no identity is not associated with anyone. Destinations that key on user id see nothing. Destinations that key on anonymous id see nothing.

{
  "event": "Item Purchased",
  "properties": { "name": "Leap to Conclusions Mat", "revenue": 14.99 },
  "timestamp": "2026-07-26T00:30:12.984Z",
  "writeKey": "seg-write-key-abc"
}
Enter fullscreen mode Exit fullscreen mode
  error   vendor.segment.body.call_needs_an_identifier
          The call carries neither `userId` nor `anonymousId`. Segment
          documents one of the two as required on every call.
    fix:  Send `userId` for a known user, or `anonymousId` for an
          unidentified one.
Enter fullscreen mode Exit fullscreen mode

writeKey in the body is optional because Segment also accepts it as HTTP basic auth. An empty writeKey field is flagged; a missing one is not, because the key may be on the wire in the header. The pack does not pretend it can see that header from a JSON fixture.

Timestamp is ISO 8601, or omit it

Segment documents timestamp as an ISO 8601 date string. An epoch number is the wrong type. For events happening now, omit the field and let Segment stamp the call.

"timestamp": 1770000000
Enter fullscreen mode Exit fullscreen mode
  error   vendor.segment.body.timestamp.invalid
          `timestamp` is `1770000000`, which does not match
          `^[0-9]{4}-[0-9]{2}-[0-9]{2}[T ][0-9]{2}:[0-9]{2}:[0-9]{2}`.
          Segment documents the timestamp as an ISO 8601 date string.
          Leave it out for events happening now.
    fix:  Send an ISO 8601 timestamp, or omit it and let Segment stamp
          the call.
Enter fullscreen mode Exit fullscreen mode

PostHog capture uses the same ISO 8601 shape, and an epoch value there is read as ingestion time rather than the time you sent. Amplitude wants milliseconds. There is no default timestamp type in this layer of the stack.

A batch also requires type on each row, and the value has to be one of identify, track, page, screen, group, or alias. An unrecognized type is an error. A missing type is an error. That contract is what makes a mixed batch lintable.

What the CLI actually prints

The pack matches api.segment.io and the regional segmentapis.com hosts on a URL, and it matches a JSON body that carries writeKey, userId, anonymousId, or batch[].type (and does not look like PostHog). You can lint a fixture with no host.

pixellint validate json @batch.json
Enter fullscreen mode Exit fullscreen mode
rulepack: core
  ok
rulepack: vendor/segment (vendor: segment)
  error   vendor.segment.body.track_requires_an_event_name  ...

1 error(s), 0 warning(s), 0 info message(s) across 2 rulepack(s).
Enter fullscreen mode Exit fullscreen mode
cargo install pixellint
# or: npm install pixellint
Enter fullscreen mode Exit fullscreen mode

The contracts, with citations, live on the Segment HTTP Tracking API pack page. Paste a body into the browser playground if you do not want a clone. Nothing you paste leaves the browser.

The short version

A track needs an event name. Every call needs userId or anonymousId. timestamp is ISO 8601, or absent. The HTTP 200 is not a contract check.

Pixellint is independent of Segment. The rule ids above cite the HTTP API and the track spec because that is where the requirements live, not because this is an official tool.

Top comments (0)