Webhooks
Primus Auth Broker can notify external services of authentication events by posting signed JSON payloads to any HTTPS endpoint you control. This enables real-time audit trails, provisioning pipelines, and analytics integrations without polling.
Enable Webhooks
Add a Webhooks section to your PrimusAuth configuration:
{
"PrimusAuth": {
"Webhooks": {
"Enabled": true,
"Endpoint": "https://hooks.example.com/auth-events",
"SigningSecret": "USE-USER-SECRETS-NOT-HERE",
"TimeoutSeconds": 5,
"Events": []
}
}
}
| Field | Type | Default | Allowed values | Description |
|---|---|---|---|---|
Enabled | bool | false | true | false | Activates webhook dispatch. |
Endpoint | string | "" | Any HTTPS URL | HTTPS URL that receives event payloads. Required when Enabled is true. |
SigningSecret | string | "" | Any string (32+ chars recommended) | Your chosen secret used to compute the HMAC-SHA256 signature on each payload. Generate a long random string — store it in user secrets or CI secret injection, never in source-controlled config files. |
TimeoutSeconds | int | 5 | Positive integer | HTTP client timeout per webhook call. Auth is delayed by up to this duration if the endpoint is slow — keep it low. |
Events | string[] | [] | Array of event name strings | Events to send. Empty array means all events. See Event Reference below for valid names. |
Environment Variable Override
ASP.NET Core maps __ to : so all config keys work as environment variables (e.g. PrimusAuth__Webhooks__Enabled=true, PrimusAuth__Webhooks__SigningSecret=your-secret).
Event Reference
All events share the same envelope:
{
"event": "user.login",
"timestamp": "2026-03-13T10:30:00.0000000+00:00",
"data": { ... }
}
Each POST also includes an X-Primus-Event: <event-name> header alongside X-Primus-Signature.
| Event | When fired | data fields |
|---|---|---|
user.login | Successful authentication (any provider) | email, provider — provider values: "local", "azure", "auth0", "google", "okta", or "saml:{scheme}". Local login also includes id. |
user.logout | Explicit sign-out | email |
user.provisioned | First-login JIT provisioning (OIDC only) | email, provider |
login.failed | Invalid credentials on local login | email, provider: "local", reason: "invalid_credentials" |
mfa.enrolled | User completes TOTP enrollment | email |
mfa.verified | User passes MFA challenge | email |
mfa.disabled | User removes TOTP from account | email |
Verifying Signatures
Every webhook POST includes an X-Primus-Signature header:
X-Primus-Signature: sha256=<hex>
On your receiving endpoint, compute the HMAC-SHA256 of the raw request body using your SigningSecret and compare it to the X-Primus-Signature header value. The format is sha256=<lowercase-hex>.
Use a constant-time comparison (e.g. CryptographicOperations.FixedTimeEquals) rather than == to prevent timing attacks. If the signatures do not match, return HTTP 401 and discard the payload.
Filtering Events
To receive only specific events, pass their names in the Events array:
{
"PrimusAuth": {
"Webhooks": {
"Enabled": true,
"Endpoint": "https://hooks.example.com/auth-events",
"SigningSecret": "...",
"Events": ["user.login", "login.failed"]
}
}
}
Events not in the list are silently dropped before the HTTP call is made.
Custom Dispatcher
By default, Primus uses HttpPrimusWebhookDispatcher (awaited inline using IHttpClientFactory). You can replace it by registering your own IPrimusWebhookDispatcher implementation before calling AddPrimusAuthBroker:
// Replace before AddPrimusAuthBroker
builder.Services.AddScoped<IPrimusWebhookDispatcher, MyAuditQueueDispatcher>();
builder.Services.AddPrimusAuthBroker(/* ... */);
The interface has a single method:
public interface IPrimusWebhookDispatcher
{
Task DispatchAsync(string eventName, object payload);
}
Behaviour Notes
- Webhook dispatch is awaited inline — if the endpoint is slow, authentication is delayed by up to
TimeoutSecondsseconds (default: 5). KeepTimeoutSecondslow, or use a queue-based custom dispatcher to decouple dispatch from the auth response entirely. - Failures (exceptions, timeouts, non-2xx responses) are caught and logged at
Warninglevel via the standard ASP.NETILogger. No retry logic is built-in; use a queue-based custom dispatcher if retry semantics are required. - The dispatcher is registered as a scoped service, so it participates in the same DI scope as the HTTP request.
Next Steps
- Integration Guide — complete setup walkthrough from install to first authenticated request
- Endpoint Reference — complete endpoint documentation with request and response shapes
- Advanced Configuration — custom audit sinks, HA clustering, and data protection
- Bot Protection & Rate Limiting — complement webhook events with IP-based rate limiting