Skip to content
Tracon

WebhookSubscription

Namespace Tracon · Assembly Tracon.Abstractions.dll

An external system’s event subscription.

public sealed record WebhookSubscription : IEquatable<WebhookSubscription>
Inheritance
object ← WebhookSubscription
Implements
IEquatable<WebhookSubscription>
Inherited Members
object.GetType(), object.ToString(), object.Equals(object?), object.Equals(object?, object?), object.ReferenceEquals(object?, object?), object.GetHashCode()

This record has no secret field. The signing secret does not sit in the database; only the name of the configuration key the value is read from (WebhookSubscription.SecretConfigurationKey) sits here, and the value is resolved at run time through IConfiguration.

public WebhookSubscription()

The consecutive-failure count. Once the threshold is exceeded, the subscription disables itself and is written to the audit trail.

public int ConsecutiveFailures { get; init; }

int

The creation time (UTC).

public required DateTimeOffset CreatedAt { get; init; }

DateTimeOffset

Whether the subscription is enabled.

public bool Enabled { get; init; }

bool

The subscribed event names. See WebhookEvents.

public required IReadOnlyList<string> Events { get; init; }

IReadOnlyList<string>

Credential headers, as a map from the header name to the NAME of the configuration key its value is read from. The value is resolved through IConfiguration on every delivery and is never stored.

public IReadOnlyDictionary<string, string> HeaderConfigurationKeys { get; init; }

IReadOnlyDictionary<string, string>

var record = new WebhookSubscription
{
Id = Guid.NewGuid(),
TenantId = "acme",
Name = "orders",
Url = "https://orders.example.com/hook",
Events = [WebhookEvents.RunCompleted],
CreatedAt = DateTimeOffset.UtcNow,
UpdatedAt = DateTimeOffset.UtcNow,
HeaderConfigurationKeys = new Dictionary<string, string>
{
["X-Api-Key"] = "Tracon:WebhookSecrets:acme:OrdersKey",
},
};

Every key name must sit under TraconWebhookOptions.AllowedConfigurationPrefix, inside the tenant’s own key space ({prefix}{tenantId}:...; a flat name directly under the prefix belongs to the default tenant). The rule is checked on save and again before every delivery; a delivery whose subscription names a key outside the tenant is dropped.

A header name may appear only once across WebhookSubscription.Headers and this map, compared case-insensitively, and may not be one of the headers Tracon sets itself (X-Tracon-*). The map counts toward TraconWebhookOptions.MaxExtraHeaders together with WebhookSubscription.Headers.

Extra headers added to every request. An HTTP response never returns a stored value: it carries every header name with the value ***.

public IReadOnlyDictionary<string, string> Headers { get; init; }

IReadOnlyDictionary<string, string>

An authentication header is not written here: the value would be stored in the database. Declare it in HeaderConfigurationKeys instead, and use WebhookSubscription.SecretConfigurationKey for signing. The HTTP save rejects a header whose name looks like a credential (Authorization, X-Api-Key, Cookie, any name ending in -key).

The read, list and save responses mask every value. A save whose header value is *** is rejected, so a client that reads, edits and saves a subscription must send every header value again.

The subscription identifier.

public required Guid Id { get; init; }

Guid

The name, unique within the tenant.

public required string Name { get; init; }

string

The name of the configuration key the signing secret is read from. Not the secret itself.

public string? SecretConfigurationKey { get; init; }

string?

Example: "Tracon:WebhookSecrets:order-service". The value lives in dotnet user-secrets or an environment variable. If empty, requests are not signed.

The tenant the subscription belongs to.

public required string TenantId { get; init; }

string

The last-updated time (UTC).

public required DateTimeOffset UpdatedAt { get; init; }

DateTimeOffset

The address events are sent to.

public required string Url { get; init; }

string

The address goes through an SSRF check: only https (or http for loopback), private network ranges are rejected, redirects are not followed.

public override bool Equals(object? obj)

obj object?

bool

public bool Equals(WebhookSubscription? other)

other WebhookSubscription?

bool

public override int GetHashCode()

int

public override string ToString()

string

operator ==(WebhookSubscription?, WebhookSubscription?)

Section titled “ operator ==(WebhookSubscription?, WebhookSubscription?)”
public static bool operator ==(WebhookSubscription? left, WebhookSubscription? right)

left WebhookSubscription?

right WebhookSubscription?

bool

operator !=(WebhookSubscription?, WebhookSubscription?)

Section titled “ operator !=(WebhookSubscription?, WebhookSubscription?)”
public static bool operator !=(WebhookSubscription? left, WebhookSubscription? right)

left WebhookSubscription?

right WebhookSubscription?

bool