Skip to main content

Overview

The Server-Sent Events (SSE) endpoint provides an alternative method for real-time notification delivery using HTTP-based server-push technology. This approach is beneficial for clients that cannot maintain WebSocket connections due to proxy limitations or require a simpler implementation.

Connection

Endpoint: GET /api/v1/client/events
Authentication: Requires Bearer token using user credentials

Connection Query Parameters

ParameterTypeRequiredDescription
tokenstringYesUser authentication token (alternative to Authorization header)
last_event_idstringNoID of the last received event for reconnection purposes
typesstringNoComma-separated list of event types to subscribe to (default: all)
client_idstringNoClient identifier for correlation (UUID recommended)
versionstringNoProtocol version for compatibility (defaults to latest)

Connection Example

// Create EventSource connection to SSE endpoint
const eventSource = new EventSource('https://atp.example.com/api/v1/client/events?token=user_token_xyz789');

// Handle connection open
eventSource.onopen = () => {
  console.log('Connected to ATP notification stream via SSE');
};

// Handle notification events
eventSource.addEventListener('notification', (event) => {
  const notification = JSON.parse(event.data);
  console.log('Received notification:', notification);
  
  // Store last event ID for reconnection
  if (event.lastEventId) {
    localStorage.setItem('last_event_id', event.lastEventId);
  }
  
  // Process notification
  handleNotification(notification);
});

// Handle status update events
eventSource.addEventListener('status_update', (event) => {
  const statusUpdate = JSON.parse(event.data);
  console.log('Status update:', statusUpdate);
  handleStatusUpdate(statusUpdate);
});

// Handle connection error
eventSource.onerror = (error) => {
  console.error('SSE connection error:', error);
  
  // Check if the connection was closed
  if (eventSource.readyState === EventSource.CLOSED) {
    console.log('Connection closed. EventSource will automatically attempt to reconnect.');
  }
};

// Function to manually close the connection if needed
function closeConnection() {
  eventSource.close();
  console.log('SSE connection closed manually');
}

Message Format

SSE messages are formatted according to the EventSource specification with event types and JSON data payloads:
event: notification
id: 1622145123-abc123
data: {"id":"550e8400-e29b-41d4-a716-446655440000","version":"1.0",...}

Event Types

The following event types are supported:
Event TypeDescription
notificationNew notification available for the user
status_updateStatus change for an existing notification
errorError condition related to the connection
pingServer heartbeat to maintain connection

Event Data Formats

Notification Event

{
  "id": "550e8400-e29b-41d4-a716-446655440000",
  "version": "1.0",
  "timestamp": "2025-05-25T10:30:00Z",
  "deadline": "2025-05-25T11:00:00Z",
  "service": {
    "id": "lovelace-ide",
    "name": "Lovelace IDE",
    "icon": "https://lovelace.dev/icon.png"
  },
  "context": {
    "title": "Deploy to Production?",
    "description": "New version 2.1.0 is ready for deployment to production servers.",
    "project": "backend-api",
    "metadata": {
      "version": "2.1.0",
      "changes": 47,
      "test_coverage": "98.3%"
    }
  },
  "actions": [
    {
      "id": "approve",
      "label": "Approve Deployment",
      "response_type": "simple",
      "flags": ["irreversible"]
    },
    {
      "id": "reject",
      "label": "Reject",
      "response_type": "text",
      "constraints": {
        "placeholder": "Reason for rejection"
      }
    }
  ]
}

Status Update Event

{
  "notification_id": "550e8400-e29b-41d4-a716-446655440000",
  "status": "invalidated",
  "reason": "The deployment was canceled by the system",
  "timestamp": "2025-05-25T10:32:15Z"
}

Error Event

{
  "code": "RATE_LIMIT_EXCEEDED",
  "message": "Client has exceeded connection rate limit",
  "request_id": "req_abc123def456"
}

Ping Event

{
  "timestamp": "2025-05-25T10:31:00Z"
}

Reconnection

One of the key advantages of Server-Sent Events is the built-in reconnection mechanism provided by the browser’s EventSource implementation. When a connection is lost, the browser will automatically attempt to reconnect with exponential backoff. The SSE protocol uses the Last-Event-ID header for resuming the stream from where it left off. When reconnecting, the client should include the last received event ID to ensure no events are missed:
// Store last event ID when receiving events
eventSource.addEventListener('notification', (event) => {
  if (event.lastEventId) {
    localStorage.setItem('last_event_id', event.lastEventId);
  }
  // Process event...
});

// When manually reconnecting, include the last event ID
function reconnect() {
  const lastEventId = localStorage.getItem('last_event_id');
  const url = `https://atp.example.com/api/v1/client/events?token=user_token_xyz789`;
  
  if (lastEventId) {
    url += `&last_event_id=${lastEventId}`;
  }
  
  if (eventSource) {
    eventSource.close();
  }
  
  eventSource = new EventSource(url);
  // Set up event listeners again...
}

Custom Reconnection Strategy

For applications that need more control over the reconnection behavior, you can implement a custom strategy:
function connectWithCustomReconnect() {
  const MAX_RETRIES = 10;
  const BASE_DELAY_MS = 1000;
  let retryCount = 0;
  let eventSource = null;
  
  function connect() {
    const lastEventId = localStorage.getItem('last_event_id');
    let url = `https://atp.example.com/api/v1/client/events?token=user_token_xyz789`;
    
    if (lastEventId) {
      url += `&last_event_id=${lastEventId}`;
    }
    
    // Close existing connection if any
    if (eventSource) {
      eventSource.close();
    }
    
    // Create new connection
    eventSource = new EventSource(url);
    
    eventSource.onopen = () => {
      console.log('Connected to ATP notification stream via SSE');
      retryCount = 0; // Reset retry counter on successful connection
    };
    
    // Set up event listeners...
    
    eventSource.onerror = (error) => {
      console.error('SSE connection error:', error);
      
      // Only handle closed connections
      if (eventSource.readyState === EventSource.CLOSED) {
        if (retryCount < MAX_RETRIES) {
          // Calculate delay with exponential backoff and jitter
          const delay = Math.min(
            BASE_DELAY_MS * Math.pow(2, retryCount) + Math.random() * 1000, 
            60000 // Cap at 60 seconds
          );
          
          console.log(`Connection closed. Retrying in ${delay}ms...`);
          retryCount++;
          setTimeout(connect, delay);
        } else {
          console.error('Maximum retry attempts reached. Giving up.');
        }
      }
    };
  }
  
  connect();
  
  // Return control functions
  return {
    close: () => {
      if (eventSource) {
        eventSource.close();
      }
    },
    reconnect: () => {
      retryCount = 0;
      connect();
    }
  };
}

const connection = connectWithCustomReconnect();

Limitations

  • Maximum of 10 concurrent SSE connections per user
  • Connection timeout: 120 seconds of inactivity
  • Reconnection delay: Controlled by client with server-suggested 3-second initial delay
  • Maximum event size: 512KB

Browser Compatibility

Server-Sent Events are supported in all modern browsers:
  • Chrome 6+
  • Firefox 6+
  • Safari 5+
  • Edge 79+
  • Opera 11.5+
For older browsers or environments without native EventSource support, polyfills are available.

Advantages Over WebSockets

While WebSockets provide bidirectional communication, SSE offers several advantages for notification delivery:
  1. Simpler Implementation: No need to handle message framing or connection state
  2. Automatic Reconnection: Built-in reconnection with message resumption
  3. Better Proxy Support: Works over standard HTTP, avoiding firewall/proxy issues
  4. Event Filtering: Subscribe to specific event types
  5. Less Overhead: Lower connection maintenance overhead for unidirectional communication

Best Practices

  1. Store Last Event ID: Always persist the last event ID for reliable reconnection
  2. Handle Backpressure: Process events asynchronously to avoid blocking the event stream
  3. Implement Timeout Handling: Close and reconnect if no events are received within the expected interval
  4. Graceful Degradation: Fall back to polling if SSE is not supported or continuously fails
  5. Connection Monitoring: Track connection state and implement health checks
  6. Memory Management: Close EventSource when component is unmounted to prevent memory leaks
I