From 2b9271d5b8348b0ddaca7dd0dc40192db8ba33b3 Mon Sep 17 00:00:00 2001 From: Anna Garcia Date: Tue, 2 Jun 2026 13:55:22 -0400 Subject: [PATCH 1/4] feat: emit $is_server property on captured events --- lib/Client.php | 1 + test/FeatureFlagLocalEvaluationTest.php | 2 +- test/FeatureFlagTest.php | 4 +-- test/PostHogTest.php | 36 ++++++++++++++++++++++--- 4 files changed, 36 insertions(+), 7 deletions(-) diff --git a/lib/Client.php b/lib/Client.php index dceac83..4202159 100644 --- a/lib/Client.php +++ b/lib/Client.php @@ -1657,6 +1657,7 @@ private function message($msg) $msg["properties"]['$lib'] = 'posthog-php'; $msg["properties"]['$lib_version'] = PostHog::VERSION; $msg["properties"]['$lib_consumer'] = $this->consumer->getConsumer(); + $msg["properties"]['$is_server'] = true; if (isset($msg["distinctId"])) { $msg["distinct_id"] = $msg["distinctId"]; diff --git a/test/FeatureFlagLocalEvaluationTest.php b/test/FeatureFlagLocalEvaluationTest.php index 676ede7..c9dd09c 100644 --- a/test/FeatureFlagLocalEvaluationTest.php +++ b/test/FeatureFlagLocalEvaluationTest.php @@ -1453,7 +1453,7 @@ public function testSimpleFlag() ), 1 => array( "path" => "/batch/", - 'payload' => '{"batch":[{"properties":{"$feature_flag":"simple-flag","$feature_flag_response":true,"$lib":"posthog-php","$lib_version":"' . PostHog::VERSION . '","$lib_consumer":"LibCurl","$groups":[]},"distinct_id":"some-distinct-id","event":"$feature_flag_called","$groups":[],"library":"posthog-php","library_version":"' . PostHog::VERSION . '","library_consumer":"LibCurl","groups":[],"timestamp":"2022-05-01T00:00:00+00:00","type":"capture"}],"api_key":"random_key"}', + 'payload' => '{"batch":[{"properties":{"$feature_flag":"simple-flag","$feature_flag_response":true,"$lib":"posthog-php","$lib_version":"' . PostHog::VERSION . '","$lib_consumer":"LibCurl","$is_server":true,"$groups":[]},"distinct_id":"some-distinct-id","event":"$feature_flag_called","$groups":[],"library":"posthog-php","library_version":"' . PostHog::VERSION . '","library_consumer":"LibCurl","groups":[],"timestamp":"2022-05-01T00:00:00+00:00","type":"capture"}],"api_key":"random_key"}', "extraHeaders" => array(0 => 'User-Agent: posthog-php/' . PostHog::VERSION), "requestOptions" => array('shouldVerify' => true), ), diff --git a/test/FeatureFlagTest.php b/test/FeatureFlagTest.php index 2ad7b10..9c10ff1 100644 --- a/test/FeatureFlagTest.php +++ b/test/FeatureFlagTest.php @@ -97,7 +97,7 @@ public function testIsFeatureEnabledCapturesFeatureFlagCalledEventWithAdditional ), 1 => array( "path" => "/batch/", - "payload" => '{"batch":[{"properties":{"$feature_flag":"simple-test","$feature_flag_response":true,"$feature_flag_request_id":"98487c8a-287a-4451-a085-299cd76228dd","$feature_flag_id":6,"$feature_flag_version":1,"$feature_flag_reason":"Matched condition set 1","$lib":"posthog-php","$lib_version":"' . PostHog::VERSION . '","$lib_consumer":"LibCurl","$groups":[]},"distinct_id":"user-id","event":"$feature_flag_called","$groups":[],"library":"posthog-php","library_version":"' . PostHog::VERSION . '","library_consumer":"LibCurl","groups":[],"timestamp":"2022-05-01T00:00:00+00:00","type":"capture"}],"api_key":"random_key"}', + "payload" => '{"batch":[{"properties":{"$feature_flag":"simple-test","$feature_flag_response":true,"$feature_flag_request_id":"98487c8a-287a-4451-a085-299cd76228dd","$feature_flag_id":6,"$feature_flag_version":1,"$feature_flag_reason":"Matched condition set 1","$lib":"posthog-php","$lib_version":"' . PostHog::VERSION . '","$lib_consumer":"LibCurl","$is_server":true,"$groups":[]},"distinct_id":"user-id","event":"$feature_flag_called","$groups":[],"library":"posthog-php","library_version":"' . PostHog::VERSION . '","library_consumer":"LibCurl","groups":[],"timestamp":"2022-05-01T00:00:00+00:00","type":"capture"}],"api_key":"random_key"}', "extraHeaders" => array(0 => 'User-Agent: posthog-php/' . PostHog::VERSION), "requestOptions" => array('shouldVerify' => true), ), @@ -196,7 +196,7 @@ public function testGetFeatureFlagCapturesFeatureFlagCalledEventWithAdditionalMe ), 1 => array( "path" => "/batch/", - "payload" => '{"batch":[{"properties":{"$feature_flag":"multivariate-test","$feature_flag_response":"variant-value","$feature_flag_request_id":"98487c8a-287a-4451-a085-299cd76228dd","$feature_flag_id":7,"$feature_flag_version":3,"$feature_flag_reason":"Matched condition set 2","$lib":"posthog-php","$lib_version":"' . PostHog::VERSION . '","$lib_consumer":"LibCurl","$groups":[]},"distinct_id":"user-id","event":"$feature_flag_called","$groups":[],"library":"posthog-php","library_version":"' . PostHog::VERSION . '","library_consumer":"LibCurl","groups":[],"timestamp":"2022-05-01T00:00:00+00:00","type":"capture"}],"api_key":"random_key"}', + "payload" => '{"batch":[{"properties":{"$feature_flag":"multivariate-test","$feature_flag_response":"variant-value","$feature_flag_request_id":"98487c8a-287a-4451-a085-299cd76228dd","$feature_flag_id":7,"$feature_flag_version":3,"$feature_flag_reason":"Matched condition set 2","$lib":"posthog-php","$lib_version":"' . PostHog::VERSION . '","$lib_consumer":"LibCurl","$is_server":true,"$groups":[]},"distinct_id":"user-id","event":"$feature_flag_called","$groups":[],"library":"posthog-php","library_version":"' . PostHog::VERSION . '","library_consumer":"LibCurl","groups":[],"timestamp":"2022-05-01T00:00:00+00:00","type":"capture"}],"api_key":"random_key"}', "extraHeaders" => array(0 => 'User-Agent: posthog-php/' . PostHog::VERSION), "requestOptions" => array('shouldVerify' => true), ), diff --git a/test/PostHogTest.php b/test/PostHogTest.php index 0b14dc8..ff3eaeb 100644 --- a/test/PostHogTest.php +++ b/test/PostHogTest.php @@ -448,6 +448,34 @@ public function testCapture(): void ); } + public function testCaptureIncludesIsServerProperty(): void + { + self::assertTrue( + PostHog::capture( + array( + "distinctId" => "john", + "event" => "Module PHP Event", + ) + ) + ); + PostHog::flush(); + + $batchCall = null; + foreach ($this->http_client->calls as $call) { + if (($call["path"] ?? null) === "/batch/") { + $batchCall = $call; + break; + } + } + self::assertNotNull($batchCall, "Expected a /batch/ call to have been made"); + + $decoded = json_decode($batchCall["payload"], true); + $properties = $decoded["batch"][0]["properties"]; + + self::assertArrayHasKey('$is_server', $properties); + self::assertTrue($properties['$is_server']); + } + public function testCaptureWithSendFeatureFlagsOption(): void { $this->executeAtFrozenDateTime(new \DateTime('2022-05-01'), function () { @@ -485,7 +513,7 @@ public function testCaptureWithSendFeatureFlagsOption(): void ), 1 => array ( "path" => "/batch/", - "payload" => '{"batch":[{"event":"Module PHP Event","send_feature_flags":true,"properties":{"$feature\/true-flag":true,"$active_feature_flags":["true-flag"],"$lib":"posthog-php","$lib_version":"' . PostHog::VERSION . '","$lib_consumer":"LibCurl"},"library":"posthog-php","library_version":"' . PostHog::VERSION . '","library_consumer":"LibCurl","distinct_id":"john","groups":[],"timestamp":"2022-05-01T00:00:00+00:00","type":"capture"}],"api_key":"random_key"}', + "payload" => '{"batch":[{"event":"Module PHP Event","send_feature_flags":true,"properties":{"$feature\/true-flag":true,"$active_feature_flags":["true-flag"],"$lib":"posthog-php","$lib_version":"' . PostHog::VERSION . '","$lib_consumer":"LibCurl","$is_server":true},"library":"posthog-php","library_version":"' . PostHog::VERSION . '","library_consumer":"LibCurl","distinct_id":"john","groups":[],"timestamp":"2022-05-01T00:00:00+00:00","type":"capture"}],"api_key":"random_key"}', "extraHeaders" => array(0 => 'User-Agent: posthog-php/' . PostHog::VERSION), "requestOptions" => array('shouldVerify' => true), ), @@ -541,7 +569,7 @@ public function testCaptureWithLocalSendFlags(): void ), 1 => array ( "path" => "/batch/", - "payload" => '{"batch":[{"event":"Module PHP Event","send_feature_flags":true,"properties":{"$feature\/true-flag":true,"$active_feature_flags":["true-flag"],"$lib":"posthog-php","$lib_version":"' . PostHog::VERSION . '","$lib_consumer":"LibCurl"},"library":"posthog-php","library_version":"' . PostHog::VERSION . '","library_consumer":"LibCurl","distinct_id":"john","groups":[],"timestamp":"2022-05-01T00:00:00+00:00","type":"capture"}],"api_key":"random_key"}', + "payload" => '{"batch":[{"event":"Module PHP Event","send_feature_flags":true,"properties":{"$feature\/true-flag":true,"$active_feature_flags":["true-flag"],"$lib":"posthog-php","$lib_version":"' . PostHog::VERSION . '","$lib_consumer":"LibCurl","$is_server":true},"library":"posthog-php","library_version":"' . PostHog::VERSION . '","library_consumer":"LibCurl","distinct_id":"john","groups":[],"timestamp":"2022-05-01T00:00:00+00:00","type":"capture"}],"api_key":"random_key"}', "extraHeaders" => array(0 => 'User-Agent: posthog-php/' . PostHog::VERSION), "requestOptions" => array('shouldVerify' => true), ), @@ -591,7 +619,7 @@ public function testCaptureWithLocalSendFlagsNoOverrides(): void ), 1 => array ( "path" => "/batch/", - "payload" => '{"batch":[{"event":"Module PHP Event","properties":{"$feature\/true-flag":"random-override","$active_feature_flags":["true-flag"],"$lib":"posthog-php","$lib_version":"' . PostHog::VERSION . '","$lib_consumer":"LibCurl"},"send_feature_flags":true,"library":"posthog-php","library_version":"' . PostHog::VERSION . '","library_consumer":"LibCurl","distinct_id":"john","groups":[],"timestamp":"2022-05-01T00:00:00+00:00","type":"capture"}],"api_key":"random_key"}', + "payload" => '{"batch":[{"event":"Module PHP Event","properties":{"$feature\/true-flag":"random-override","$active_feature_flags":["true-flag"],"$lib":"posthog-php","$lib_version":"' . PostHog::VERSION . '","$lib_consumer":"LibCurl","$is_server":true},"send_feature_flags":true,"library":"posthog-php","library_version":"' . PostHog::VERSION . '","library_consumer":"LibCurl","distinct_id":"john","groups":[],"timestamp":"2022-05-01T00:00:00+00:00","type":"capture"}],"api_key":"random_key"}', "extraHeaders" => array(0 => 'User-Agent: posthog-php/' . PostHog::VERSION), "requestOptions" => array('shouldVerify' => true), ), @@ -893,7 +921,7 @@ public function testCaptureWithSendFeatureFlagsFalse(): void ), 1 => array ( "path" => "/batch/", - "payload" => '{"batch":[{"event":"Module PHP Event","send_feature_flags":false,"properties":{"$lib":"posthog-php","$lib_version":"' . PostHog::VERSION . '","$lib_consumer":"LibCurl"},"library":"posthog-php","library_version":"' . PostHog::VERSION . '","library_consumer":"LibCurl","distinct_id":"john","groups":[],"timestamp":"2022-05-01T00:00:00+00:00","type":"capture"}],"api_key":"random_key"}', + "payload" => '{"batch":[{"event":"Module PHP Event","send_feature_flags":false,"properties":{"$lib":"posthog-php","$lib_version":"' . PostHog::VERSION . '","$lib_consumer":"LibCurl","$is_server":true},"library":"posthog-php","library_version":"' . PostHog::VERSION . '","library_consumer":"LibCurl","distinct_id":"john","groups":[],"timestamp":"2022-05-01T00:00:00+00:00","type":"capture"}],"api_key":"random_key"}', "extraHeaders" => array(0 => 'User-Agent: posthog-php/' . PostHog::VERSION), "requestOptions" => array('shouldVerify' => true), ), From f2b7f6b5cc99f6e5047c5284e3661578b1731dfb Mon Sep 17 00:00:00 2001 From: Anna Garcia Date: Tue, 2 Jun 2026 15:50:45 -0400 Subject: [PATCH 2/4] chore: add changeset --- .changeset/is-server-property.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/is-server-property.md diff --git a/.changeset/is-server-property.md b/.changeset/is-server-property.md new file mode 100644 index 0000000..96b3931 --- /dev/null +++ b/.changeset/is-server-property.md @@ -0,0 +1,5 @@ +--- +"posthog-php": patch +--- + +Emit `$is_server` property on captured events so PostHog can identify server-side events. From c67397ca46fede9900e9ba28214cc834885fedca Mon Sep 17 00:00:00 2001 From: Anna Garcia Date: Tue, 2 Jun 2026 17:32:38 -0400 Subject: [PATCH 3/4] feat: make $is_server configurable (default true) --- .changeset/is-server-property.md | 2 +- lib/Client.php | 10 +++++++- test/PostHogTest.php | 39 ++++++++++++++++++++++++++++++++ 3 files changed, 49 insertions(+), 2 deletions(-) diff --git a/.changeset/is-server-property.md b/.changeset/is-server-property.md index 96b3931..fd4b988 100644 --- a/.changeset/is-server-property.md +++ b/.changeset/is-server-property.md @@ -2,4 +2,4 @@ "posthog-php": patch --- -Emit `$is_server` property on captured events so PostHog can identify server-side events. +Add a configurable `$is_server` event property (default `true`) so PostHog can identify server-side events. Set `is_server` to `false` when using posthog-php as a client/CLI so the device OS is attributed normally. diff --git a/lib/Client.php b/lib/Client.php index 4202159..ba2c8ab 100644 --- a/lib/Client.php +++ b/lib/Client.php @@ -121,6 +121,7 @@ class Client implements FeatureFlagEvaluationsHost * compress_request?: bool|string, * error_handler?: callable, * filename?: string, + * is_server?: bool, * error_tracking?: array{ * enabled?: bool, * capture_errors?: bool, @@ -1657,7 +1658,14 @@ private function message($msg) $msg["properties"]['$lib'] = 'posthog-php'; $msg["properties"]['$lib_version'] = PostHog::VERSION; $msg["properties"]['$lib_consumer'] = $this->consumer->getConsumer(); - $msg["properties"]['$is_server'] = true; + + // When running as a server SDK (the default), tag events as server-side so + // PostHog does not attribute the host machine's device/OS to the event. + // Set the `is_server` option to false when using posthog-php as a + // client/CLI so the device OS is attributed normally. + if (($this->options['is_server'] ?? true) === true) { + $msg["properties"]['$is_server'] = true; + } if (isset($msg["distinctId"])) { $msg["distinct_id"] = $msg["distinctId"]; diff --git a/test/PostHogTest.php b/test/PostHogTest.php index ff3eaeb..130f5d4 100644 --- a/test/PostHogTest.php +++ b/test/PostHogTest.php @@ -476,6 +476,45 @@ public function testCaptureIncludesIsServerProperty(): void self::assertTrue($properties['$is_server']); } + public function testCaptureOmitsIsServerPropertyWhenDisabled(): void + { + $this->http_client = new MockedHttpClient("app.posthog.com"); + $this->client = new Client( + self::FAKE_API_KEY, + [ + "debug" => true, + "is_server" => false, + ], + $this->http_client, + "test" + ); + PostHog::init(null, null, $this->client); + + self::assertTrue( + PostHog::capture( + array( + "distinctId" => "john", + "event" => "Module PHP Event", + ) + ) + ); + PostHog::flush(); + + $batchCall = null; + foreach ($this->http_client->calls as $call) { + if (($call["path"] ?? null) === "/batch/") { + $batchCall = $call; + break; + } + } + self::assertNotNull($batchCall, "Expected a /batch/ call to have been made"); + + $decoded = json_decode($batchCall["payload"], true); + $properties = $decoded["batch"][0]["properties"]; + + self::assertArrayNotHasKey('$is_server', $properties); + } + public function testCaptureWithSendFeatureFlagsOption(): void { $this->executeAtFrozenDateTime(new \DateTime('2022-05-01'), function () { From 890ef7ea6cb74385990c03942ffd756756acfe10 Mon Sep 17 00:00:00 2001 From: Anna Garcia Date: Wed, 3 Jun 2026 10:52:42 -0400 Subject: [PATCH 4/4] chore: bump changeset to minor (new is_server option) --- .changeset/is-server-property.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.changeset/is-server-property.md b/.changeset/is-server-property.md index fd4b988..eb2bda9 100644 --- a/.changeset/is-server-property.md +++ b/.changeset/is-server-property.md @@ -1,5 +1,5 @@ --- -"posthog-php": patch +"posthog-php": minor --- Add a configurable `$is_server` event property (default `true`) so PostHog can identify server-side events. Set `is_server` to `false` when using posthog-php as a client/CLI so the device OS is attributed normally.