Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/is-server-property.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"PostHog": minor
---

Add a configurable `$is_server` event property (default `true`) so PostHog can identify server-side events. Set `PostHogOptions.IsServer` to `false` when using the SDK as a client/CLI so the device OS is attributed normally.
6 changes: 6 additions & 0 deletions src/PostHog/Api/PostHogApiClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -285,6 +285,12 @@ void PrepareAndMutatePayload(Dictionary<string, object> payload)

properties.Merge(_options.Value.SuperProperties);

// Stamp $is_server last so a super property can't override the SDK's server/client classification.
if (_options.Value.IsServer)
{
properties[PostHogProperties.IsServer] = true;
}

payload["properties"] = properties;

// Only set timestamp if one isn't already provided in properties
Expand Down
5 changes: 5 additions & 0 deletions src/PostHog/Api/PostHogProperties.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,11 @@ public static class PostHogProperties
/// </summary>
public const string Lib = "$lib";

/// <summary>
/// The property name indicating that the event was captured by a server-side SDK.
/// </summary>
public const string IsServer = "$is_server";

/// <summary>
/// The property name for the client library version.
/// </summary>
Expand Down
14 changes: 14 additions & 0 deletions src/PostHog/Config/PostHogOptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,20 @@ public string? ProjectApiKey
/// </remarks>
public IReadOnlyList<string>? EvaluationContexts { get; set; }

/// <summary>
/// Whether captured events are attributed to a server. (Default: true)
/// </summary>
/// <remarks>
/// When <c>true</c> (the default), every captured event includes the <c>$is_server</c> property set to
/// <c>true</c> so PostHog identifies the event as server-side. Set this to <c>false</c> when using the SDK
/// as a client or CLI so the device's operating system is attributed normally instead of being treated as a
/// server. When <c>false</c>, the <c>$is_server</c> property is omitted entirely.
/// <code>
/// var options = new PostHogOptions { ProjectToken = "phc_...", IsServer = false };
/// </code>
/// </remarks>
public bool IsServer { get; set; } = true;

/// <summary>
/// Whether this client is disabled and should no-op instead of sending data to PostHog. (Default: false)
/// </summary>
Expand Down
6 changes: 6 additions & 0 deletions src/PostHog/PostHogClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -313,6 +313,12 @@ bool CaptureCore(

capturedEvent.Properties.Merge(_options.Value.SuperProperties);

// Stamp $is_server last so a super property can't override the SDK's server/client classification.
if (_options.Value.IsServer)
{
capturedEvent.Properties[PostHogProperties.IsServer] = true;
}

var batchItem = new BatchItem<CapturedEvent, CapturedEventBatchContext>(BatchTask);

if (_asyncBatchHandler.Enqueue(batchItem))
Expand Down
60 changes: 40 additions & 20 deletions tests/UnitTests/Features/FeatureFlagsTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -197,7 +197,8 @@ public async Task CapturesFeatureFlagCalledEventOnlyOncePerDistinctIdFlagKeyAndR
"distinct_id": "a-distinct-id",
"$lib": "posthog-dotnet",
"$lib_version": "{{client.Version}}",
"$geoip_disable": true
"$geoip_disable": true,
"$is_server": true
},
"timestamp": "2024-01-21T19:08:23\u002B00:00"
},
Expand All @@ -215,7 +216,8 @@ public async Task CapturesFeatureFlagCalledEventOnlyOncePerDistinctIdFlagKeyAndR
"distinct_id": "another-distinct-id",
"$lib": "posthog-dotnet",
"$lib_version": "{{client.Version}}",
"$geoip_disable": true
"$geoip_disable": true,
"$is_server": true
},
"timestamp": "2024-01-21T19:08:23\u002B00:00"
},
Expand All @@ -233,7 +235,8 @@ public async Task CapturesFeatureFlagCalledEventOnlyOncePerDistinctIdFlagKeyAndR
"distinct_id": "another-distinct-id",
"$lib": "posthog-dotnet",
"$lib_version": "{{client.Version}}",
"$geoip_disable": true
"$geoip_disable": true,
"$is_server": true
},
"timestamp": "2024-01-21T19:08:23\u002B00:00"
}
Expand Down Expand Up @@ -290,7 +293,8 @@ public async Task CapturesFeatureFlagCalledEventOnlyOncePerDistinctIdFlagKeyAndR
"distinct_id": "a-distinct-id",
"$lib": "posthog-dotnet",
"$lib_version": "{{client.Version}}",
"$geoip_disable": true
"$geoip_disable": true,
"$is_server": true
},
"timestamp": "2024-01-21T19:08:23\u002B00:00"
},
Expand All @@ -306,7 +310,8 @@ public async Task CapturesFeatureFlagCalledEventOnlyOncePerDistinctIdFlagKeyAndR
"distinct_id": "another-distinct-id",
"$lib": "posthog-dotnet",
"$lib_version": "{{client.Version}}",
"$geoip_disable": true
"$geoip_disable": true,
"$is_server": true
},
"timestamp": "2024-01-21T19:08:23\u002B00:00"
},
Expand All @@ -322,7 +327,8 @@ public async Task CapturesFeatureFlagCalledEventOnlyOncePerDistinctIdFlagKeyAndR
"distinct_id": "another-distinct-id",
"$lib": "posthog-dotnet",
"$lib_version": "{{client.Version}}",
"$geoip_disable": true
"$geoip_disable": true,
"$is_server": true
},
"timestamp": "2024-01-21T19:08:23\u002B00:00"
}
Expand Down Expand Up @@ -498,7 +504,8 @@ await client.IsFeatureEnabledAsync(
"distinct_id": "659df793-429a-4517-84ff-747dfc103e6c",
"$lib": "posthog-dotnet",
"$lib_version": "{{VersionConstants.Version}}",
"$geoip_disable": true
"$geoip_disable": true,
"$is_server": true
},
"timestamp": "2024-01-21T19:08:23\u002B00:00"
}
Expand Down Expand Up @@ -549,7 +556,8 @@ public async Task CapturesFeatureFlagCalledEventWithRequestIdWhenPresent()
"distinct_id": "a-distinct-id",
"$lib": "posthog-dotnet",
"$lib_version": "{{client.Version}}",
"$geoip_disable": true
"$geoip_disable": true,
"$is_server": true
},
"timestamp": "2024-01-21T19:08:23\u002B00:00"
}
Expand Down Expand Up @@ -601,7 +609,8 @@ public async Task CapturesFeatureFlagCalledEventWithoutEvaluatedAtWhenNotPresent
"distinct_id": "a-distinct-id",
"$lib": "posthog-dotnet",
"$lib_version": "{{client.Version}}",
"$geoip_disable": true
"$geoip_disable": true,
"$is_server": true
},
"timestamp": "2024-01-21T19:08:23\u002B00:00"
}
Expand Down Expand Up @@ -729,7 +738,8 @@ public async Task CapturesFeatureFlagCalledEventWithAdditionalMetadataIdWhenPres
"distinct_id": "a-distinct-id",
"$lib": "posthog-dotnet",
"$lib_version": "{{client.Version}}",
"$geoip_disable": true
"$geoip_disable": true,
"$is_server": true
},
"timestamp": "2024-01-21T19:08:23\u002B00:00"
}
Expand Down Expand Up @@ -2455,7 +2465,8 @@ public async Task CapturesFeatureFlagCalledEvent()
"distinct_id": "a-distinct-id",
"$lib": "posthog-dotnet",
"$lib_version": "{{client.Version}}",
"$geoip_disable": true
"$geoip_disable": true,
"$is_server": true
},
"timestamp": "2024-01-21T19:08:23\u002B00:00"
}
Expand Down Expand Up @@ -2508,7 +2519,8 @@ public async Task CapturesFeatureFlagCalledEventWithGroupInformation()
"$groups": {
"company": "id:5",
"department": "id:3"
}
},
"$is_server": true
},
"timestamp": "2024-01-21T19:08:23\u002B00:00"
}
Expand Down Expand Up @@ -2569,7 +2581,8 @@ await client.GetFeatureFlagAsync("another-flag-key",
"distinct_id": "a-distinct-id",
"$lib": "posthog-dotnet",
"$lib_version": "{{client.Version}}",
"$geoip_disable": true
"$geoip_disable": true,
"$is_server": true
},
"timestamp": "2024-01-21T19:08:23\u002B00:00"
},
Expand All @@ -2585,7 +2598,8 @@ await client.GetFeatureFlagAsync("another-flag-key",
"distinct_id": "another-distinct-id",
"$lib": "posthog-dotnet",
"$lib_version": "{{client.Version}}",
"$geoip_disable": true
"$geoip_disable": true,
"$is_server": true
},
"timestamp": "2024-01-21T19:08:24\u002B00:00"
},
Expand All @@ -2601,7 +2615,8 @@ await client.GetFeatureFlagAsync("another-flag-key",
"distinct_id": "another-distinct-id",
"$lib": "posthog-dotnet",
"$lib_version": "{{client.Version}}",
"$geoip_disable": true
"$geoip_disable": true,
"$is_server": true
},
"timestamp": "2024-01-21T19:08:25\u002B00:00"
},
Expand All @@ -2617,7 +2632,8 @@ await client.GetFeatureFlagAsync("another-flag-key",
"distinct_id": "a-distinct-id",
"$lib": "posthog-dotnet",
"$lib_version": "{{client.Version}}",
"$geoip_disable": true
"$geoip_disable": true,
"$is_server": true
},
"timestamp": "2024-01-21T19:08:27\u002B00:00"
}
Expand Down Expand Up @@ -2673,7 +2689,8 @@ public async Task CapturesFeatureFlagCalledEventAgainIfCacheSlidingWindowExpirat
"distinct_id": "a-distinct-id",
"$lib": "posthog-dotnet",
"$lib_version": "{{client.Version}}",
"$geoip_disable": true
"$geoip_disable": true,
"$is_server": true
},
"timestamp": "2024-01-21T19:08:23\u002B00:00"
},
Expand All @@ -2689,7 +2706,8 @@ public async Task CapturesFeatureFlagCalledEventAgainIfCacheSlidingWindowExpirat
"distinct_id": "another-distinct-id",
"$lib": "posthog-dotnet",
"$lib_version": "{{client.Version}}",
"$geoip_disable": true
"$geoip_disable": true,
"$is_server": true
},
"timestamp": "2024-01-21T19:08:24\u002B00:00"
},
Expand All @@ -2705,7 +2723,8 @@ public async Task CapturesFeatureFlagCalledEventAgainIfCacheSlidingWindowExpirat
"distinct_id": "another-distinct-id",
"$lib": "posthog-dotnet",
"$lib_version": "{{client.Version}}",
"$geoip_disable": true
"$geoip_disable": true,
"$is_server": true
},
"timestamp": "2024-01-21T19:08:25\u002B00:00"
},
Expand All @@ -2721,7 +2740,8 @@ public async Task CapturesFeatureFlagCalledEventAgainIfCacheSlidingWindowExpirat
"distinct_id": "a-distinct-id",
"$lib": "posthog-dotnet",
"$lib_version": "{{client.Version}}",
"$geoip_disable": true
"$geoip_disable": true,
"$is_server": true
},
"timestamp": "2024-01-21T19:08:27\u002B00:00"
}
Expand Down
Loading
Loading