Skip to content

Fix WooCommerce identity stitching for anonymous server events (0.9.1)#1

Merged
ddavidebor merged 2 commits into
mainfrom
claude/posthog-identity-stitching-fix-26i15n
Jun 25, 2026
Merged

Fix WooCommerce identity stitching for anonymous server events (0.9.1)#1
ddavidebor merged 2 commits into
mainfrom
claude/posthog-identity-stitching-fix-26i15n

Conversation

@ddavidebor

Copy link
Copy Markdown

Problem

The 0.9.0 release fixed purchase firing (events now reconcile 1:1 with WooCommerce orders), but the funnel was still fragmented because of an identity-stitching bug.

Every server-side commerce event resolved its distinct id through WooEvents::visitor_distinct_id():

return '' !== $resolved['distinct_id'] ? $resolved['distinct_id'] : wp_generate_uuid4();

When a visitor was anonymous and the posthog-js cookie couldn't be read server-side, this minted a fresh random UUID per event. So one cookieless visitor — or one cookieless bot hitting several endpoints — became a separate phantom person for product_viewed, product_added_to_cart, cart_viewed, checkout_viewed and order_placed. The result: inflated unique-person counts and a broken funnel (e.g. more unique persons at checkout_viewed than at product_viewed).

Fix

visitor_distinct_id() now resolves in strict precedence and never invents a person:

  1. Logged-in user's stable id, then the posthog-js cookie id (stitches server events to the same person as the browser) — unchanged primary path.
  2. New: the WooCommerce session id (wc_sess_<id>), but only when a real session exists (has_session() — a persisted session cookie or a logged-in user), so the id is stable across the visit's requests. A freshly generated session id (no cookie yet) is treated as no id, since it's as ephemeral as a random uuid.
  3. Otherwise return '' and let the dispatcher's existing empty-id guard (ServerClient::guard()) drop the event, rather than attribute it to a phantom person.

Also clarified the Person profiles setting help copy (the setting already exists end-to-end in the Tracking tab; default unchanged at identified_only). Switching it to Everyone/always is the recommended lever to also resolve the client/server profile-modeling mismatch for stores that want anonymous person-based funnels.

Verification

  • phpcs (WordPress Coding Standards) clean across all files.
  • PHPUnit: 34 tests, 91 assertions — green.
  • Version bumped to 0.9.1 (tagbridge.php, readme.txt) with a changelog entry.

Recommended follow-ups (no code change)

  • Turn on bot filtering in the PostHog funnel.
  • For chinesecleavers: Tracking tab → Person profiles → Everyone to get fully-merged anonymous funnels.

🤖 Generated with Claude Code

https://claude.ai/code/session_011PFz4sZbxsq17nQKfAGQ5g


Generated by Claude Code

claude added 2 commits June 24, 2026 22:47
…se 0.9.1

Server-side commerce events resolved their distinct id through
WooEvents::visitor_distinct_id(), which fell back to wp_generate_uuid4()
whenever the visitor was anonymous and the posthog-js cookie could not be
read server-side. That minted a brand-new random id per event, so one
cookieless visitor — or one cookieless bot hitting several endpoints —
became a separate phantom person for product_viewed, product_added_to_cart,
cart_viewed, checkout_viewed and order_placed. The result was inflated
unique-person counts and a broken funnel (e.g. more unique persons at
checkout_viewed than at product_viewed).

Anonymous server events now reuse the stable posthog-js cookie id when
present, fall back to the WooCommerce session customer id only when a real
session exists (so the id is stable across the visit's requests), and are
otherwise dropped via the dispatcher's empty-id guard rather than attributed
to an invented person. A freshly generated session id (no session cookie yet)
is treated as no id, since it is as ephemeral as a random uuid.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011PFz4sZbxsq17nQKfAGQ5g
Explain both modes and their tradeoff: "Identified only" profiles only
logged-in users (PostHog's default, fewest billable profiles); "Everyone"
also profiles anonymous visitors so anonymous funnels and server-side
commerce events stitch into one person, paired with bot filtering. The
shipped default stays identified_only.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011PFz4sZbxsq17nQKfAGQ5g
@ddavidebor ddavidebor merged commit 6252b01 into main Jun 25, 2026
4 checks passed
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