SignalR Integration
CritterWatch uses SignalR to push live updates from the console to the browser. As an operator, you don't need to know any of this — the browser handles it. As an integrator who wants to consume the live feed from your own client, this page covers the contract.
Where it lives
The SignalR hub is mounted at /api/messages (configurable via UseCritterWatch(signalRRoute: "...")). It's a one-way push channel — the browser receives messages but never sends commands over SignalR. All operator commands go through the HTTP API at /api/critterwatch/*.
Connection lifecycle
The browser connects via the standard @microsoft/signalr JavaScript client with automatic reconnect. On reconnect, the client requests a full state snapshot from the HTTP API to catch up on anything missed during the disconnection window.
The connection indicator in the header tells you which state you're in:
- Green — connected and receiving live updates.
- Yellow — reconnecting (brief blip, automatic recovery).
- Red — disconnected; the page is showing stale data.
What gets pushed
Every event that should reach the browser is relayed onto the hub immediately after the console persists it. The major message types:
| Message | What it carries |
|---|---|
service_updated | Full service state snapshot (after a telemetry batch is applied) |
agent_health_updated | Agent health changes |
alert_raised / alert_elevated / alert_resolved / alert_cleared | Alert lifecycle |
shard_states_updated | Projection shard sequences |
persistence_counts_updated | Inbox / outbox / scheduled / DLQ counts |
back_pressure_triggered / circuit_breaker_tripped | Endpoint resilience events |
timeline_entry | Activity timeline entry |
Plus on-demand response messages — handler source code, HTTP chain source code, pending-migration probe results, tenant list refresh — surfaced when the browser opens the corresponding detail page.
Wire format
All messages use the Wolverine CloudEvents envelope:
{
"type": "service_updated",
"data": {
"serviceName": "trip-service",
...
}
}The type discriminator is snake_case derived from the C# message type. Properties inside data are camelCase. Enums serialize as string names ("Query", not 1).
This serialization is configured by the Wolverine SignalR transport and applies to every message on the hub.
Subscribing from a custom client
If you want a server-side or out-of-browser client to subscribe to the live feed:
- Add the standard SignalR client for your platform.
- Connect to
https://<your-console>/api/messages(or your custom route). - Authenticate the same way browsers do — typically via a cookie or bearer token, depending on how you've configured authentication on the console.
- Listen for the message types above.
The hub uses a single dispatch method so most platforms handle it via a generic on(type, handler) registration. For .NET clients (Microsoft.AspNetCore.SignalR.Client), you'd register handlers for each type you care about.
Throttling and back pressure
The console doesn't currently throttle the SignalR push rate. A single very busy service can drive a high message rate per connection — keep that in mind if you're building a custom client that does heavy per-message work. The browser SPA buffers updates per Pinia store and reconciles them on the next reactivity tick, which is what keeps the UI smooth on bursty services.
If you're seeing browser CPU or network spikes, see Store Inspector → message log to confirm which message types are noisy.
