Real-Time Events (SSE) Reference
BRIDGEPORT exposes a Server-Sent Events endpoint for live updates — health status changes, deployment progress, new notifications, metric refreshes, and container discovery events stream to connected clients without polling.
Connecting
Section titled “Connecting”Endpoint
Section titled “Endpoint”GET /api/eventsReturns an unbounded text/event-stream response. Keep the connection open for the lifetime of the session.
Authentication
Section titled “Authentication”The browser EventSource API does not support custom request headers, so authentication is passed as a query parameter. BRIDGEPORT accepts two token formats and tries them in order:
- API token — A long-lived token minted by an admin via
POST /api/admin/tokens(or the Admin → Integrations UI). Validated by hashing and looking up in the database. Recommended for scripts and server-side consumers. - JWT — The short-lived session token returned by
POST /api/auth/login. Suitable for browser sessions where the JWT is already in memory.
If both checks fail, the connection is rejected with 401 Unauthorized before any SSE headers are written.
The token appears in the URL, which means it can show up in server access logs and browser history. Prefer API tokens over JWTs for long-running integrations, and scope access logs appropriately on your reverse proxy.
Query Parameters
Section titled “Query Parameters”| Parameter | Required | Description |
|---|---|---|
token | Yes | JWT or API token for authentication |
environmentId | No | Scope events to a single environment |
Examples
Connect without filtering (receives events across all environments):
GET /api/events?token=bp_api_xxxxxxxxxxxxConnect scoped to a single environment:
GET /api/events?token=bp_api_xxxxxxxxxxxx&environmentId=env_abc123Event Types
Section titled “Event Types”Each event is delivered in standard SSE format:
event: <type>data: <JSON payload>The event field matches one of the five named types below. Clients should ignore unknown event types to remain forward-compatible.
health_status
Section titled “health_status”Fired when a scheduled health check produces a status change for a server or service.
Payload
| Field | Type | Description |
|---|---|---|
resourceType | "server" | "service" | Whether the check targeted a server or a container service |
resourceId | string | ID of the server or service |
status | string | New health status (e.g., "healthy", "unhealthy", "degraded") |
environmentId | string | Environment the resource belongs to |
Example
{ "resourceType": "service", "resourceId": "svc_9xk2mw", "status": "unhealthy", "environmentId": "env_abc123"}When it fires
- After each scheduled server health check cycle completes
- When the agent pushes metrics that include health data
deployment_progress
Section titled “deployment_progress”Fired at key transitions during a deployment: start, success, and failure. Both standalone service deployments and orchestrated deployment plan steps emit this event.
Payload
| Field | Type | Description |
|---|---|---|
deploymentId | string | undefined | ID of the Deployment record (present for single-service deploys) |
planId | string | undefined | ID of the DeploymentPlan (present for orchestrated plan steps) |
serviceId | string | ID of the service being deployed |
status | string | One of: "deploying", "success", "failed", "running", "completed" |
environmentId | string | Environment the deployment belongs to |
Examples
Single-service deployment starting:
{ "deploymentId": "dep_7rtn4q", "serviceId": "svc_9xk2mw", "status": "deploying", "environmentId": "env_abc123"}Orchestrated plan step completing:
{ "planId": "plan_2kmpf1", "serviceId": "svc_9xk2mw", "status": "completed", "environmentId": "env_abc123"}Status values by emitter
| Source | Status values |
|---|---|
deploy.ts (individual) | deploying, success, failed |
orchestration.ts (plan) | running, completed, failed |
notification
Section titled “notification”Fired when a new in-app notification is created for a specific user. Unlike other event types, notification events are user-scoped: a connected client only receives events where userId matches its own authenticated user ID. Other users’ notification events are silently dropped.
Payload
| Field | Type | Description |
|---|---|---|
userId | string | ID of the user the notification was created for |
count | number | Always 1 — signals that one new notification arrived |
Example
{ "userId": "usr_4hn8vp", "count": 1}When it fires
Any time BRIDGEPORT creates a notification for the authenticated user: deployment failures, health state changes that trigger alerts, or other system events with notification types configured in Admin > Notifications.
metrics_updated
Section titled “metrics_updated”Fired after a fresh set of server metrics has been collected and stored. Clients listening for this event should re-fetch the metrics they care about rather than relying on the event payload itself.
Payload
| Field | Type | Description |
|---|---|---|
serverId | string | ID of the server whose metrics were updated |
environmentId | string | Environment the server belongs to |
Example
{ "serverId": "srv_3qw8xz", "environmentId": "env_abc123"}When it fires
On each metrics collection cycle for the server. The interval depends on the metrics mode:
- SSH polling: controlled globally by the
SCHEDULER_METRICS_INTERVALenv var (default: 300 s) - Agent push: controlled by the agent’s
-intervalflag (default: 30s)
container_discovery
Section titled “container_discovery”Fired after BRIDGEPORT’s scheduled container discovery scan completes for a server. The event signals that the server’s container list may have changed; clients should re-fetch service data if they display live container state.
Payload
| Field | Type | Description |
|---|---|---|
serverId | string | ID of the server that was scanned |
environmentId | string | Environment the server belongs to |
Example
{ "serverId": "srv_3qw8xz", "environmentId": "env_abc123"}When it fires
On each container discovery cycle (interval controlled globally by the SCHEDULER_DISCOVERY_INTERVAL env var, default: 300 s).
Environment Filtering
Section titled “Environment Filtering”Passing environmentId as a query parameter scopes the stream to a single environment. The filter is applied server-side: events whose payload does not contain a matching environmentId are dropped before being written to the response.
The notification event type does not carry an environmentId field. It is exempt from environment filtering and is always delivered based on user match.
Without filtering: the client receives events from all environments. Useful for admin dashboards.
With filtering: only events from the specified environment are delivered. This is the correct mode for most UI pages that operate within a single environment.
Client Integration Examples
Section titled “Client Integration Examples”Basic Connection
Section titled “Basic Connection”const token = 'bp_api_xxxxxxxxxxxx'; // or your JWTconst environmentId = 'env_abc123';
const es = new EventSource( `/api/events?token=${encodeURIComponent(token)}&environmentId=${environmentId}`);
es.addEventListener('health_status', (e) => { const data = JSON.parse(e.data); console.log(`[health] ${data.resourceType} ${data.resourceId} -> ${data.status}`);});
es.addEventListener('deployment_progress', (e) => { const data = JSON.parse(e.data); console.log(`[deploy] service ${data.serviceId} -> ${data.status}`);});
es.addEventListener('notification', (e) => { const data = JSON.parse(e.data); console.log(`[notification] ${data.count} new for user ${data.userId}`);});
es.addEventListener('metrics_updated', (e) => { const data = JSON.parse(e.data); console.log(`[metrics] server ${data.serverId} updated`);});
es.addEventListener('container_discovery', (e) => { const data = JSON.parse(e.data); console.log(`[discovery] server ${data.serverId} scanned`);});
es.onerror = (err) => { console.error('SSE error:', err);};Typed Event Handlers
Section titled “Typed Event Handlers”For TypeScript projects, define equivalent types on the client:
type HealthStatusData = { resourceType: 'server' | 'service'; resourceId: string; status: string; environmentId: string;};
type DeploymentProgressData = { deploymentId?: string; planId?: string; serviceId: string; status: string; environmentId: string;};
type NotificationData = { userId: string; count: number;};
type MetricsUpdatedData = { serverId: string; environmentId: string;};
type ContainerDiscoveryData = { serverId: string; environmentId: string;};
function parseEvent<T>(e: MessageEvent): T { return JSON.parse(e.data) as T;}
const es = new EventSource(`/api/events?token=${token}&environmentId=${envId}`);
es.addEventListener('health_status', (e) => { const data = parseEvent<HealthStatusData>(e); // data is fully typed});
es.addEventListener('deployment_progress', (e) => { const data = parseEvent<DeploymentProgressData>(e);});Reconnection and Error Handling
Section titled “Reconnection and Error Handling”EventSource reconnects automatically after a dropped connection with browser-managed exponential backoff (typically starting at 3 seconds). You do not need to implement reconnection logic manually.
const es = new EventSource(`/api/events?token=${token}`);
es.onopen = () => { console.log('SSE connected');};
es.onerror = (e) => { // readyState 0 = CONNECTING (browser is retrying) // readyState 2 = CLOSED (gave up) if (es.readyState === EventSource.CLOSED) { console.error('SSE connection closed permanently'); }};
// Clean up when done (e.g., component unmount)function disconnect() { es.close();}Infrastructure Notes
Section titled “Infrastructure Notes”Response headers
Content-Type: text/event-streamCache-Control: no-cacheConnection: keep-aliveX-Accel-Buffering: noX-Accel-Buffering: no instructs nginx to disable proxy buffering for this response. Without it, nginx buffers chunks until a threshold is reached, causing events to arrive in delayed bursts. Ensure your reverse proxy configuration does not override this header.
Keepalive comments
An :ok comment is sent immediately on connect to flush the response headers through intermediate proxies. A :keepalive comment is sent every 30 seconds. These are transparent to EventSource listeners but prevent idle connections from being closed by firewalls and load balancers with aggressive timeouts.
Concurrent connection limit
The event bus sets EventEmitter.setMaxListeners(100), meaning Node.js will not warn below 100 concurrent SSE subscribers. If your deployment exceeds 100 simultaneous clients (e.g., a large team with multiple open tabs), increase this value in src/lib/event-bus.ts.
Each SSE client holds an open HTTP connection and an active EventEmitter listener. Under high concurrency, ensure your Node.js process and reverse proxy have sufficient file descriptor limits and connection timeouts.
Related Docs
Section titled “Related Docs”- API Reference — REST API authentication and endpoints
- Agent Reference — Monitoring agent that triggers
metrics_updatedevents - Environment Settings — Monitoring intervals that affect event frequency