Admin Hub — Real-time SignalR Push to the Admin UI
The Admin Hub is a SignalR fan-out channel that lets the Admin UI surface operator-relevant events the moment they happen, instead of waiting for the next page refresh or scheduled poll. It complements the Notification Center (bell-icon dropdown + transient toaster): incoming push events are translated into notifications, so the bell counter, dropdown list, and toaster all light up automatically with no per-component wiring.
This document is the operator-side reference for the channel.
Phase status
-
Phase 1 (this release). Channel + publisher + UI subscriber all ship. The hub is mapped, the Admin UI subscribes at startup, and a
POST /api/admin/hub/testendpoint fires a synthetic event end-to-end. -
Phase 2 (next release). The four places that actually fire events get wired:
- Run history — fire
run.completedwhen a run transitions toCompleted/Failed. - Admin and object audit —
fire
audit.appendedafter every successful insert. - Relay agent monitoring — fire
relay.status_changedon connect, disconnect, heartbeat-timeout. - The Sync Engine — fire
connector.erroron the terminal-error path.
The wire format is fixed in phase 1 so phase 2 doesn’t need a UI redeploy.
- Run history — fire
Endpoint
GET (negotiate) wss://<api>/hubs/admin
- Auth.
audit.read— same permission that protects the audit log + Ops Center pages, so any operator who can already see those signals on a page refresh also gets them pushed live. - Direction. Server -> client only. The hub has no client-callable
methods; calling any method via
connection.invoke(...)will fail. - Transport. WebSocket where available; LongPolling fallback for Negotiate/Windows-auth deployments where the WebSocket upgrade can’t carry the SPNEGO ticket.
- Reconnect. The UI uses
withAutomaticReconnect()with backoff[0, 1, 2, 5, 10, 30] s. After thirty seconds of failed reconnect attempts the channel goes silent until the next page navigation.
Event types
run.completed
Fires when a RunHistory row transitions to Completed or Failed.
{
"runId": "<guid>",
"connectorId": "<guid>",
"connectorName": "Entra Production",
"status": "Completed", // or "Failed"
"addedCount": 12,
"updatedCount": 3,
"deletedCount": 0,
"errorMessage": null // populated on Failed
}
UI behaviour: success notification when status === "Completed",
error notification when status === "Failed". Click-through goes
to /identity/connectors?id=<connectorId>.
audit.appended
Fires when a new row is appended to IM_AdminAudit or IM_ObjectAudit.
{
"table": "admin", // or "object"
"auditId": 4711,
"when": "2026-04-26T14:32:09Z",
"action": "ConnectorCreate",
"actor": "CONTOSO\\jdoe"
}
UI behaviour: info notification; click-through goes to
/identity/audit.
relay.status_changed
Fires when a relay agent connects, disconnects, or its heartbeat
times out (the Admin API’s relay monitor marks a
relay offline after RelayAgents:HeartbeatTimeoutSeconds).
{
"agentId": "<guid>",
"agentName": "London-DC1",
"status": "Online", // or "Offline"
"timestamp": "2026-04-26T14:32:09Z"
}
UI behaviour: info on Online, warning on Offline; click-through
goes to /identity/operations (the Ops Center page that hosts the
relay-agent table).
connector.error
Fires when a connector run hits a terminal error (i.e. the engine
short-circuits before producing a run.completed).
{
"connectorId": "<guid>",
"connectorName": "Workday HR",
"error": "Token endpoint returned 401",
"occurredAt": "2026-04-26T14:32:09Z"
}
UI behaviour: error notification, sticky by default — operator
must acknowledge it in the bell dropdown before it dismisses, so a
transient connector failure can’t be missed by an inattentive eye.
Click-through goes to /identity/connectors?id=<connectorId>.
End-to-end verification
A small operator-only test endpoint lets you confirm the channel works without waiting for a real run:
POST /api/admin/hub/test
Authorization: Negotiate <ticket> # or Bearer <jwt>
Returns {"fired": true} and emits a single synthetic
connector.error event. Any UI client currently subscribed to
/hubs/admin will pop a sticky red toast titled
“Connector error — DEMO” and add a matching entry to the bell
dropdown.
curl example (Negotiate)
curl -X POST --negotiate -u : \
https://identitymesh.contoso.com/api/admin/hub/test
curl example (Bearer / JWT)
curl -X POST \
-H "Authorization: Bearer $TOKEN" \
https://identitymesh.contoso.com/api/admin/hub/test
If you get a 401 or 403, the calling identity doesn’t have the
audit.read permission — assign it through Roles or via the Roles
mapping config and retry.
Auth model
The hub mirrors the rest of the API surface; there’s no separate configuration:
- Negotiate / Windows-auth. The browser sends the SPNEGO cookie
on the negotiate request via
withCredentials: true. The same cookie carries through to the WebSocket upgrade. - JWT / Entra. The UI acquires an Entra access token silently
and supplies it via SignalR’s
accessTokenFactory. The hub accepts the token in the?access_token=...query string when the path starts with/hubs/(browsers can’t set theAuthorizationheader on a WebSocket upgrade); regular API endpoints still require the header form.
CORS
The Admin API’s CORS policy covers /hubs/* already
(credentials allowed + the configured origin list); no
hub-specific CORS configuration is needed. Update
IdentityMesh:CorsOrigins in appsettings.json when deploying
the UI to a new origin.
Operational notes
- The hub is fan-out only — every subscribed client gets every event. There is no per-tenant or per-connector filtering today. If two operators are signed in, both get the same notification. Operators-as-tenants filtering is a phase 3 follow-up.
- Notifications are transient. The Notification Center keeps the most recent fifty in browser memory and resets them on page reload. Persistent notifications (with a server-side queue and read/unread state per operator) is a separate feature.
accessTokenFactoryruns on every reconnect attempt. MSAL’s silent-token cache means this is cheap (memory hit) until the token expires; expect one extra refresh round-trip per hour per active operator.
Cross-references
siem-integration.md— server-side audit / event export to a SIEM. The Admin Hub is the operator-facing complement: same source events, different destination.audit-chain.md— tamper-evident chain overIM_AdminAudit/IM_ObjectAudit. Phase 2 wiring ofaudit.appendedfires after the chain extension, so the push event is causally consistent with what the chain verifier sees.relay-agent.md— the relay-side hub (/hubs/relay) is a separate channel. The Admin Hub consumes the same status transitions but exposes them to human operators rather than to the agent itself.