Skip to content

feat(tracing-headers): allow an optional posthog-js cookie fallback - #116

Open
DanielVisca wants to merge 1 commit into
mainfrom
posthog/tracing-headers-cookie-fallback
Open

DanielVisca wants to merge 1 commit into
mainfrom
posthog/tracing-headers-cookie-fallback

Conversation

@DanielVisca

Copy link
Copy Markdown

Problem

Server SDKs link backend events to a browser session only through the X-POSTHOG-* tracing headers, and most apps never set tracing_headers. posthog-js already keeps the distinct ID, the session ID and the user state in its first-party cookie, and the browser sends that cookie on every same-site request. posthog-python and posthog-node now add an off-by-default option to read it, but the spec lists only headers as server input.

Changes

This adds three requirements to tracing-headers, with scenarios:

  • Optional posthog-js cookie fallback: the option is off by default, the middleware uses headers only when any header is present, and the cookie identity is never used for authentication.
  • Cookie fallback identity and consent: the middleware reads only the project's cookie (with the posthog-js PL/SL/EQ token replacement), honors the consent cookie and opt-out by default, and uses the distinct ID only for identified visitors.
  • Cookie fallback session liveness: the session ID is used only when it is still live, with absolute ages, a 24-hour cap, and an idle timeout clamped to 60 seconds through 10 hours, like posthog-js.

The change is synced into openspec/specs/tracing-headers/spec.md and archived under openspec/changes/archive/2026-10-09-add-optional-posthog-js-cookie-fallback/.

Implementations:

How did you test this?

openspec validate tracing-headers --type spec --strict passes. openspec validate --specs --strict (CLI 1.14.1) reports the same failures in other specs with and without this change.


Created with PostHog from a Slack thread

🤖 Generated with Claude Code

Server middleware may read the session id, and the distinct id of an identified visitor, from the posthog-js cookie when a request has neither tracing header. The option is off by default, honors the consent cookie, and checks session liveness like posthog-js.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

Generated-By: PostHog Desktop
Task-Id: 80f44d9e-704d-4f9c-93d2-bd37d55ab1c2

@dustinbyrne dustinbyrne left a comment •

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The optional cookie fallback and session-liveness checks look good. One point for consideration: retain anonymous cookie identity while avoiding new profile creation from that fallback.

AI-assisted review.


### Requirement: Cookie fallback identity and consent

With the fallback on, the middleware SHALL read only the configured project's cookie, `ph_<token>_posthog` with `+`, `/`, `=` in the token replaced by `PL`, `SL`, `EQ`. It SHALL ignore the cookie when the consent cookie `__ph_opt_in_out_<token>` is no-like, or absent under configured opt-out by default. It SHALL use the cookie `distinct_id` only when `$user_state` is `identified`.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[P2/design] Retain anonymous cookie distinct IDs

Consider retaining the cookie’s distinct_id for anonymous visitors as well, with $process_person_profile: false when that anonymous cookie identity is used.

The cookie already exposes $user_state. Discarding the distinct ID means captures get generated identities, losing browser/backend identity correlation. That is not necessary to prevent profile creation—the event’s $process_person_profile property controls that independently.

This follows Next’s current identity propagation. Preserve existing explicit-capture and authenticated-identity precedence; do not blanket-disable person processing when a different identity overrides the cookie.

An existing profile can still be associated with the event or trigger the backend’s force_upgrade behavior. The intended guarantee is avoiding new profile creation from the anonymous fallback, not guaranteeing every such event remains classified as personless.

Please update the identified-only clause, anonymous-visitor scenario, and corresponding archived artifacts.

Acceptance sketch—not executed: Enable fallback, grant consent, omit tracing headers, and provide a live cookie with distinct_id: anon-123 and $user_state: anonymous. Capture without explicit identity. Expect distinct_id: anon-123, the cookie session ID, and $process_person_profile: false. The current scenario instead requires a generated distinct ID.

@dustinbyrne dustinbyrne Oct 10, 2026 •

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

chatted with my agent regarding the above. overall, this spec is essentially how @posthog/next is currently working (with some small deviations; though, i'd be open to aligning against the details here). excited to see that you've landed on a similar approach here and it'd be great to get this formalized.

curious what your take on this is

@dustinbyrne
dustinbyrne requested a review from a team October 10, 2026 02:48
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants