diff --git a/.changeset/flag-called-session-attribution-allowlist.md b/.changeset/flag-called-session-attribution-allowlist.md new file mode 100644 index 0000000..76c891b --- /dev/null +++ b/.changeset/flag-called-session-attribution-allowlist.md @@ -0,0 +1,5 @@ +--- +"posthog-php": patch +--- + +Keep session-attribution properties on minimized `$feature_flag_called` events. `$referring_domain`, `utm_source`, `utm_medium`, `utm_campaign`, `utm_content`, `utm_term`, `gad_source`, `mc_cid`, `gclid`, and `fbclid` now survive the minimal-event allowlist, so a minimized event that happens to be a session's first event no longer nulls out that session's attribution in web analytics. Full `$referrer` stays excluded. diff --git a/lib/Client.php b/lib/Client.php index 84c6b6b..61f9566 100644 --- a/lib/Client.php +++ b/lib/Client.php @@ -30,6 +30,11 @@ class Client implements FeatureFlagEvaluationsHost * Keep in sync with the $feature_flag_* properties built in doGetFeatureFlagResult() and * FeatureFlagEvaluations::recordAccess(). A new flag-eval property added there but omitted * here is silently stripped from minimized events. + * + * The session-attribution entries are kept because web analytics reads a session's initial + * attribution from the first event in that session, and a minimized event can be that first + * event — stripping them would null out attribution for the whole session. Full $referrer + * stays out; only $referring_domain and the bare campaign/click-id keys survive. */ private const MINIMAL_FLAG_CALLED_EVENT_PROPERTIES = [ '$feature_flag', @@ -49,6 +54,16 @@ class Client implements FeatureFlagEvaluationsHost '$lib_version', '$is_server', '$release_id', + '$referring_domain', + 'utm_source', + 'utm_medium', + 'utm_campaign', + 'utm_content', + 'utm_term', + 'gad_source', + 'mc_cid', + 'gclid', + 'fbclid', ]; private const CONSUMERS = [ diff --git a/test/FeatureFlagTest.php b/test/FeatureFlagTest.php index 149bc83..996255b 100644 --- a/test/FeatureFlagTest.php +++ b/test/FeatureFlagTest.php @@ -553,6 +553,55 @@ public function testMinimalFlagCalledEventKeepsOnlyAllowlistedProperties() $this->assertSame($expected, $properties); } + public function testMinimalFlagCalledEventKeepsSessionAttributionProperties() + { + $response = MockedResponses::FLAGS_V2_RESPONSE; + $response['minimalFlagCalledEvents'] = true; + $response['flags']['simple-test']['metadata']['has_experiment'] = false; + $this->setUp($response, personalApiKey: null); + + PostHog::withContext([ + 'properties' => [ + '$referring_domain' => 'news.ycombinator.com', + '$referrer' => 'https://news.ycombinator.com/item?id=1', + 'utm_source' => 'hn', + 'utm_medium' => 'referral', + 'utm_campaign' => 'launch', + 'utm_content' => 'sidebar', + 'utm_term' => 'feature flags', + 'gad_source' => '1', + 'mc_cid' => 'abc123', + 'gclid' => 'gclid-value', + 'fbclid' => 'fbclid-value', + 'custom_property' => 'stripped', + ], + ], function (): void { + $this->assertTrue(PostHog::isFeatureEnabled('simple-test', 'user-id')); + }); + PostHog::flush(); + + $payload = json_decode($this->http_client->calls[1]['payload'], true); + $properties = $payload['batch'][0]['properties']; + + // Web analytics reads a session's initial attribution from the session's first event, + // which a minimized $feature_flag_called event can be. + $this->assertSame('news.ycombinator.com', $properties['$referring_domain']); + $this->assertSame('hn', $properties['utm_source']); + $this->assertSame('referral', $properties['utm_medium']); + $this->assertSame('launch', $properties['utm_campaign']); + $this->assertSame('sidebar', $properties['utm_content']); + $this->assertSame('feature flags', $properties['utm_term']); + $this->assertSame('1', $properties['gad_source']); + $this->assertSame('abc123', $properties['mc_cid']); + $this->assertSame('gclid-value', $properties['gclid']); + $this->assertSame('fbclid-value', $properties['fbclid']); + + // Full $referrer stays excluded, and everything outside the allowlist is still stripped. + $this->assertArrayNotHasKey('$referrer', $properties); + $this->assertArrayNotHasKey('custom_property', $properties); + $this->assertArrayNotHasKey('$lib_consumer', $properties); + } + public static function fullFlagCalledEventCases(): array { // Minimization requires both the server gate and an explicit has_experiment=false