Fix WooCommerce identity stitching for anonymous server events (0.9.1)#1
Merged
Merged
Conversation
…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
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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():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_viewedandorder_placed. The result: inflated unique-person counts and a broken funnel (e.g. more unique persons atcheckout_viewedthan atproduct_viewed).Fix
visitor_distinct_id()now resolves in strict precedence and never invents a person: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.''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/alwaysis 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.tagbridge.php,readme.txt) with a changelog entry.Recommended follow-ups (no code change)
🤖 Generated with Claude Code
https://claude.ai/code/session_011PFz4sZbxsq17nQKfAGQ5g
Generated by Claude Code