Skip to content

Commit d9d59a5

Browse files
feat: add SecretKey config, deprecate PersonalApiKey (#257)
* feat: add SecretKey config, deprecate PersonalApiKey Add PostHogOptions.SecretKey, which accepts a Personal API Key or a Project Secret API Key for local flag evaluation and remote config. PersonalApiKey becomes a deprecated alias; SecretKey wins when both set. * chore: address Greptile - SecretKey log message + forward-path config tests * docs(dotnet): use SecretKey in samples
1 parent ce287a2 commit d9d59a5

22 files changed

Lines changed: 167 additions & 64 deletions

.changeset/secret-key-config.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'PostHog': minor
3+
---
4+
5+
Add `PostHogOptions.SecretKey` for local feature flag evaluation and remote config. It accepts either a Personal API Key (`phx_...`) or a Project Secret API Key (`phs_...`). The existing `PersonalApiKey` option is now a deprecated alias; when both are set, `SecretKey` takes precedence.

samples/HogTied.Web/Pages/Index.cshtml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -47,17 +47,17 @@
4747
</strong>
4848
</div>
4949

50-
if (Model.PersonalApiKeyIsSet) {
50+
if (Model.SecretKeyIsSet) {
5151
<div class="alert alert-info mt-3" role="alert">
5252
<strong>
53-
The Personal API Key is set. If it's valid, we'll be using local evaluation.
53+
The Secret Key is set. If it's valid, we'll be using local evaluation.
5454
</strong>
5555
</div>
5656
}
5757
else {
5858
<div class="alert alert-info mt-3" role="alert">
5959
<strong>
60-
The Personal API Key is NOT set. We'll be using remote evaluation.
60+
The Secret Key is NOT set. We'll be using remote evaluation.
6161
</strong>
6262
</div>
6363
}

samples/HogTied.Web/Pages/Index.cshtml.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ public class IndexModel(IOptions<PostHogOptions> options, IPostHogClient posthog
2424

2525
public bool ProjectTokenIsSet { get; private set; }
2626

27-
public bool PersonalApiKeyIsSet { get; private set; }
27+
public bool SecretKeyIsSet { get; private set; }
2828

2929
public bool? NonExistentFlag { get; private set; }
3030

@@ -66,7 +66,7 @@ public class IndexModel(IOptions<PostHogOptions> options, IPostHogClient posthog
6666
public async Task OnGetAsync()
6767
{
6868
ProjectTokenIsSet = options.Value.ProjectToken is not (null or []);
69-
PersonalApiKeyIsSet = options.Value.PersonalApiKey is not (null or []);
69+
SecretKeyIsSet = options.Value.SecretKey is not (null or []);
7070

7171
// Check if the user is authenticated and get their user id.
7272
UserId = User.Identity?.IsAuthenticated == true

samples/PostHog.Example.Console/.env.example

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,10 @@
44
# Your project token (found on the /project/settings page in PostHog)
55
POSTHOG_PROJECT_TOKEN=phc_your_project_token_here
66

7-
# Your personal API key (for local evaluation and other advanced features)
7+
# Your secret key (for local evaluation and other advanced features)
8+
# Accepts either a Personal API Key (phx_...) or a Project Secret API Key (phs_...)
89
# Found on https://us.posthog.com/settings/user-api-keys
9-
POSTHOG_PERSONAL_API_KEY=phx_your_personal_api_key_here
10+
POSTHOG_SECRET_KEY=phx_your_secret_key_here
1011

1112
# PostHog host URL (default is US cloud)
1213
# Use https://eu.i.posthog.com for EU cloud

samples/PostHog.Example.Console/Program.cs

Lines changed: 15 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@
2828
// Get configuration from environment variables
2929
var projectToken = Environment.GetEnvironmentVariable("POSTHOG_PROJECT_TOKEN")
3030
?? Environment.GetEnvironmentVariable("POSTHOG_PROJECT_API_KEY");
31-
var personalApiKey = Environment.GetEnvironmentVariable("POSTHOG_PERSONAL_API_KEY");
31+
var secretKey = Environment.GetEnvironmentVariable("POSTHOG_SECRET_KEY");
3232
var endpoint = Environment.GetEnvironmentVariable("POSTHOG_HOST") ?? "https://us.i.posthog.com";
3333

3434
// Check credentials
@@ -58,15 +58,15 @@
5858

5959
Console.WriteLine("✅ PostHog credentials loaded successfully!");
6060
Console.WriteLine($" Project Token: ✅ Configured");
61-
Console.WriteLine($" Personal API Key: {(string.IsNullOrEmpty(personalApiKey) ? "❌ Not set (local evaluation disabled)" : "✅ Configured")}");
61+
Console.WriteLine($" Secret Key: {(string.IsNullOrEmpty(secretKey) ? "❌ Not set (local evaluation disabled)" : "✅ Configured")}");
6262
Console.WriteLine($" Endpoint: {endpoint}");
6363
Console.WriteLine();
6464

6565
// Create PostHog options
6666
var options = new PostHogOptions
6767
{
6868
ProjectToken = projectToken,
69-
PersonalApiKey = personalApiKey,
69+
SecretKey = secretKey,
7070
HostUrl = new Uri(endpoint),
7171
FlushAt = 1, // Flush immediately for demo purposes
7272
FlushInterval = TimeSpan.FromSeconds(1)
@@ -87,7 +87,7 @@
8787
// Display menu
8888
while (true)
8989
{
90-
ShowMenu(hasPersonalApiKey: !string.IsNullOrEmpty(personalApiKey));
90+
ShowMenu(hasSecretKey: !string.IsNullOrEmpty(secretKey));
9191
var choice = Prompt("\nEnter your choice (1-6): ");
9292

9393
switch (choice)
@@ -102,10 +102,10 @@
102102
await RunFeatureFlagExamples(posthog);
103103
break;
104104
case "4":
105-
await RunLocalEvaluationExample(posthog, hasPersonalApiKey: !string.IsNullOrEmpty(personalApiKey));
105+
await RunLocalEvaluationExample(posthog, hasSecretKey: !string.IsNullOrEmpty(secretKey));
106106
break;
107107
case "5":
108-
await RunAllExamples(posthog, hasPersonalApiKey: !string.IsNullOrEmpty(personalApiKey));
108+
await RunAllExamples(posthog, hasSecretKey: !string.IsNullOrEmpty(secretKey));
109109
break;
110110
case "6":
111111
Console.WriteLine("👋 Goodbye!");
@@ -131,14 +131,14 @@
131131
Console.WriteLine();
132132
}
133133

134-
static void ShowMenu(bool hasPersonalApiKey)
134+
static void ShowMenu(bool hasSecretKey)
135135
{
136136
Console.WriteLine("🦔 PostHog .NET SDK Demo - Choose an example to run:");
137137
Console.WriteLine();
138138
Console.WriteLine("1. Capture events");
139139
Console.WriteLine("2. Identify users");
140140
Console.WriteLine("3. Feature flags (remote evaluation)");
141-
Console.WriteLine($"4. Local evaluation with ETag polling{(hasPersonalApiKey ? "" : " (requires Personal API Key)")}");
141+
Console.WriteLine($"4. Local evaluation with ETag polling{(hasSecretKey ? "" : " (requires Secret Key)")}");
142142
Console.WriteLine("5. Run all examples");
143143
Console.WriteLine("6. Exit");
144144
}
@@ -283,17 +283,17 @@ static async Task RunFeatureFlagExamples(PostHogClient posthog)
283283
}
284284
}
285285

286-
static async Task RunLocalEvaluationExample(PostHogClient posthog, bool hasPersonalApiKey)
286+
static async Task RunLocalEvaluationExample(PostHogClient posthog, bool hasSecretKey)
287287
{
288288
Console.WriteLine("\n" + new string('=', 60));
289289
Console.WriteLine("LOCAL EVALUATION WITH ETAG POLLING");
290290
Console.WriteLine(new string('=', 60));
291291

292-
if (!hasPersonalApiKey)
292+
if (!hasSecretKey)
293293
{
294-
Console.WriteLine("\n⚠️ This example requires a Personal API Key to be set.");
295-
Console.WriteLine(" Set POSTHOG_PERSONAL_API_KEY in your .env file.");
296-
Console.WriteLine(" Personal API keys can be created at:");
294+
Console.WriteLine("\n⚠️ This example requires a Secret Key to be set.");
295+
Console.WriteLine(" Set POSTHOG_SECRET_KEY in your .env file.");
296+
Console.WriteLine(" Secret keys can be created at:");
297297
Console.WriteLine(" https://us.posthog.com/settings/user-api-keys");
298298
return;
299299
}
@@ -368,12 +368,12 @@ static async Task RunLocalEvaluationExample(PostHogClient posthog, bool hasPerso
368368
Console.WriteLine(" ✓ Polling stopped");
369369
}
370370

371-
static async Task RunAllExamples(PostHogClient posthog, bool hasPersonalApiKey)
371+
static async Task RunAllExamples(PostHogClient posthog, bool hasSecretKey)
372372
{
373373
Console.WriteLine("\n🔄 Running all examples…");
374374

375375
await RunCaptureExamples(posthog);
376376
await RunIdentifyExamples(posthog);
377377
await RunFeatureFlagExamples(posthog);
378-
await RunLocalEvaluationExample(posthog, hasPersonalApiKey);
378+
await RunLocalEvaluationExample(posthog, hasSecretKey);
379379
}

src/PostHog/Api/PostHogApiClient.cs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -217,9 +217,9 @@ public async Task<LocalEvaluationResponse> GetFeatureFlagsForLocalEvaluationAsyn
217217
async Task<T?> GetAuthenticatedResponseAsync<T>(string relativeUrl, CancellationToken cancellationToken)
218218
{
219219
var options = _options.Value ?? throw new InvalidOperationException(nameof(_options));
220-
var personalApiKey = options.PersonalApiKey
220+
var personalApiKey = options.SecretKey
221221
?? throw new InvalidOperationException(
222-
"This API requires that a Personal API Key is set.");
222+
"This API requires that a SecretKey (Personal API Key or Project Secret API Key) is set.");
223223

224224
var endpointUrl = new Uri(HostUrl, relativeUrl);
225225

@@ -242,9 +242,9 @@ async Task<LocalEvaluationResponse> GetAuthenticatedResponseWithETagAsync<T>(
242242
where T : LocalEvaluationApiResult
243243
{
244244
var options = _options.Value ?? throw new InvalidOperationException(nameof(_options));
245-
var personalApiKey = options.PersonalApiKey
245+
var personalApiKey = options.SecretKey
246246
?? throw new InvalidOperationException(
247-
"This API requires that a Personal API Key is set.");
247+
"This API requires that a SecretKey (Personal API Key or Project Secret API Key) is set.");
248248

249249
var endpointUrl = new Uri(HostUrl, relativeUrl);
250250

src/PostHog/Config/PostHogOptions.cs

Lines changed: 26 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@ public sealed class PostHogOptions : IOptions<PostHogOptions>
1111
{
1212
string? _projectToken;
1313
string? _projectApiKey;
14+
string? _secretKey;
15+
string? _personalApiKey;
1416

1517
/// <summary>
1618
/// The PostHog project token that identifies which project this client works with.
@@ -32,7 +34,8 @@ internal void Normalize()
3234
{
3335
_projectToken = _projectToken.NullIfEmpty();
3436
_projectApiKey = _projectApiKey.NullIfEmpty();
35-
PersonalApiKey = PersonalApiKey.NullIfEmpty();
37+
_secretKey = _secretKey.NullIfEmpty();
38+
_personalApiKey = _personalApiKey.NullIfEmpty();
3639
HostUrl = HostUrl.NormalizeHostUrl();
3740
}
3841

@@ -47,17 +50,34 @@ public string? ProjectApiKey
4750
}
4851

4952
/// <summary>
50-
/// Optional personal API key for local feature flag evaluation.
53+
/// Optional secret key used for local feature flag evaluation and remote config. Accepts either a Personal
54+
/// API Key (<c>phx_...</c>) or a Project Secret API Key (<c>phs_...</c>).
5155
/// </summary>
5256
/// <remarks>
53-
/// You can find this https://us.posthog.com/project/{YOUR_PROJECT_ID}/settings/user-api-keys
57+
/// You can find these at https://us.posthog.com/project/{YOUR_PROJECT_ID}/settings/user-api-keys
5458
/// When developing an ASP.NET Core project locally, we recommend setting this in your user secrets.
5559
/// <c>
56-
/// dotnet user-secrets --project your/project/path.csproj set PostHog:PersonalApiKey YOUR_PERSONAL_API_KEY
60+
/// dotnet user-secrets --project your/project/path.csproj set PostHog:SecretKey YOUR_SECRET_KEY
5761
/// </c>
5862
/// In other cases, use an appropriate secrets manager, configuration provider, or environment variable.
63+
/// When both <see cref="SecretKey"/> and the deprecated <see cref="PersonalApiKey"/> are set,
64+
/// <see cref="SecretKey"/> takes precedence.
5965
/// </remarks>
60-
public string? PersonalApiKey { get; set; }
66+
public string? SecretKey
67+
{
68+
get => _secretKey ?? _personalApiKey;
69+
set => _secretKey = value;
70+
}
71+
72+
/// <summary>
73+
/// Obsolete alias for <see cref="SecretKey"/>.
74+
/// </summary>
75+
[Obsolete("Use SecretKey instead. SecretKey accepts a Personal API Key or a Project Secret API Key.")]
76+
public string? PersonalApiKey
77+
{
78+
get => _secretKey ?? _personalApiKey;
79+
set => _personalApiKey = value;
80+
}
6181

6282
/// <summary>
6383
/// Evaluation contexts for feature flags.
@@ -109,7 +129,7 @@ public string? ProjectApiKey
109129
public Func<CapturedEvent, CapturedEvent?>? BeforeSend { get; set; }
110130

111131
/// <summary>
112-
/// When <see cref="PersonalApiKey"/> is set, this is the interval to poll for feature flags used in
132+
/// When <see cref="SecretKey"/> is set, this is the interval to poll for feature flags used in
113133
/// local evaluation. Default is 30 seconds.
114134
/// </summary>
115135
public TimeSpan FeatureFlagPollInterval { get; set; } = TimeSpan.FromSeconds(30);

src/PostHog/Examples.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ public static class Examples
1313
{
1414
ProjectToken = "<ph_project_token>",
1515
HostUrl = new Uri("<ph_client_api_host>"),
16-
PersonalApiKey = Environment.GetEnvironmentVariable("PostHog__PersonalApiKey"),
16+
SecretKey = Environment.GetEnvironmentVariable("PostHog__SecretKey"),
1717
});
1818

1919
/// <summary>

src/PostHog/Features/FeatureFlagOptions.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,14 +25,14 @@ public class AllFeatureFlagsOptions
2525
/// Whether to only evaluate the flag locally. Defaults to <c>false</c>.
2626
/// </summary>
2727
/// <remarks>
28-
/// Local evaluation requires that <see cref="PostHogOptions.PersonalApiKey"/> is set.
28+
/// Local evaluation requires that <see cref="PostHogOptions.SecretKey"/> is set.
2929
/// </remarks>
3030
public bool OnlyEvaluateLocally { get; init; }
3131

3232
/// <summary>
3333
/// The set of person properties used to evaluate feature flags. Required for both local and remote evaluation
3434
/// when feature flags have conditions based on person properties.
35-
/// For local evaluation (when <see cref="PostHogOptions.PersonalApiKey"/> is present and
35+
/// For local evaluation (when <see cref="PostHogOptions.SecretKey"/> is present and
3636
/// <see cref="OnlyEvaluateLocally"/> is <c>true</c>), these properties are used directly.
3737
/// For remote evaluation, these properties are sent to PostHog's servers and can override stored person properties.
3838
/// </summary>

src/PostHog/Features/LocalFeatureFlagsLoader.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -52,9 +52,9 @@ void StartPollingIfNotStarted()
5252
/// <exception cref="ApiException">Thrown when the API returns a <c>quota_limited</c> error.</exception>
5353
public async ValueTask<LocalEvaluator?> GetFeatureFlagsForLocalEvaluationAsync(CancellationToken cancellationToken)
5454
{
55-
if (string.IsNullOrWhiteSpace(options.Value.PersonalApiKey))
55+
if (string.IsNullOrWhiteSpace(options.Value.SecretKey))
5656
{
57-
// Local evaluation is not enabled since it requires a personal api key.
57+
// Local evaluation is not enabled since it requires a secret key.
5858
return null;
5959
}
6060
if (_localEvaluator is { } localEvaluator)
@@ -72,7 +72,7 @@ void StartPollingIfNotStarted()
7272
/// <returns>The local evaluator with the feature flags.</returns>
7373
public async ValueTask<LocalEvaluator?> RefreshAsync(CancellationToken cancellationToken)
7474
{
75-
if (string.IsNullOrWhiteSpace(options.Value.PersonalApiKey))
75+
if (string.IsNullOrWhiteSpace(options.Value.SecretKey))
7676
{
7777
return null;
7878
}

0 commit comments

Comments
 (0)