Sse Last Event Id
title: "SSE Last-Event-ID" description: "Learn how the Last-Event-ID header enables state recovery in Server-Sent Events, allowing clients to resume interrupted event streams." weight: 20 date: 2026-06-28 lastmod: 2026-06-28 tags: ["apis", "sse"]
The Last-Event-ID mechanism allows SSE clients to resume event streams from where they left off after a disconnection. This enables reliable event delivery and state recovery without data loss.
## What You'll Learn
- How Last-Event-ID works
- Sending event IDs from the server
- Reading Last-Event-ID header on reconnect
- Implementing event replay/recovery
- Managing ID state on the client
## Why It Matters
Without Last-Event-ID, clients lose events that occurred during disconnection. For applications that need reliable event delivery, implementing ID-based recovery is essential.
## Real-World Use
A financial trading platform sends event IDs with every market data update. If a trader's connection drops, the server replays missed events when they reconnect, ensuring no trade signals are lost.
## Flow Chart
```mermaid
sequenceDiagram
participant C as Client
participant S as Server
C->>S: GET /events
S-->>C: id: 1 data: ...
S-->>C: id: 2 data: ...
S-->>C: id: 3 data: ...
Note over C,S: Connection Lost
C->>C: Records lastEventId = 3
Note over C,S: Reconnection
C->>S: GET /events
C->>S: Last-Event-ID: 3
S-->>C: id: 3 data: ... (replayed)
S-->>C: id: 4 data: ...
Code Examples
Example 1: Server with Event ID Replay
const http = require('http');
// Event store
const eventStore = [];
const MAX_EVENTS = 5000;
let globalId = 0;
// Generate events periodically
function generateEvent() {
globalId++;
const event = {
id: `evt-${globalId}`,
data: {
id: globalId,
type: 'update',
value: Math.random(),
timestamp: Date.now(),
},
};
eventStore.push(event);
if (eventStore.length > MAX_EVENTS) {
eventStore.shift();
}
}
setInterval(generateEvent, 500);
http.createServer((req, res) => {
if (req.url !== '/events') {
res.writeHead(404);
res.end();
return;
}
// Read Last-Event-ID from header
const lastEventId = req.headers['last-event-id'];
const lastSeq = lastEventId
? parseInt(lastEventId.replace('evt-', ''))
: 0;
res.writeHead(200, {
'Content-Type': 'text/event-stream',
'Cache-Control': 'no-cache',
});
// Replay missed events
const missedEvents = eventStore.filter(e => {
const eventSeq = parseInt(e.id.replace('evt-', ''));
return eventSeq > lastSeq;
});
if (missedEvents.length > 0) {
console.log(`Replaying ${missedEvents.length} missed events`);
missedEvents.forEach(event => {
res.write(`id: ${event.id}\ndata: ${JSON.stringify(event.data)}\n\n`);
});
}
// Subscribe to new events
const listener = (event) => {
res.write(`id: ${event.id}\ndata: ${JSON.stringify(event.data)}\n\n`);
};
// In production, use an event emitter instead
const interval = setInterval(() => {
const recentEvents = eventStore.filter(e => e.id.startsWith('evt-'));
if (recentEvents.length > 0) {
const lastEvent = recentEvents[recentEvents.length - 1];
const eventSeq = parseInt(lastEvent.id.replace('evt-', ''));
if (eventSeq > lastSeq) {
lastSeq = eventSeq;
res.write(`id: ${lastEvent.id}\ndata: ${JSON.stringify(lastEvent.data)}\n\n`);
}
}
}, 500);
req.on('close', () => {
clearInterval(interval);
});
}).listen(3000);
Expected output: Server replays missed events on reconnection based on the Last-Event-ID header.
Example 2: Client-Side ID Tracking
class ReliableEventSource {
constructor(url) {
this.url = url;
this.lastEventId = localStorage.getItem('sse-last-id') || null;
this.listeners = new Map();
this.connect();
}
connect() {
const url = this.lastEventId
? `${this.url}?lastId=${this.lastEventId}`
: this.url;
this.source = new EventSource(url);
this.source.onopen = () => {
console.log('Connected, last ID:', this.lastEventId);
this.emit('connected');
};
this.source.onmessage = (event) => {
// Track last event ID
if (event.lastEventId) {
this.lastEventId = event.lastEventId;
localStorage.setItem('sse-last-id', event.lastEventId);
}
this.emit('message', JSON.parse(event.data));
};
this.source.onerror = () => {
// EventSource will auto-reconnect with Last-Event-ID header
this.emit('reconnecting');
};
}
on(event, callback) {
if (!this.listeners.has(event)) {
this.listeners.set(event, new Set());
}
this.listeners.get(event).add(callback);
return () => this.listeners.get(event)?.delete(callback);
}
emit(event, data) {
this.listeners.get(event)?.forEach(cb => cb(data));
}
clearRecovery() {
this.lastEventId = null;
localStorage.removeItem('sse-last-id');
}
close() {
this.source.close();
}
}
// Usage with persistent recovery
const sse = new ReliableEventSource('/events');
sse.on('message', (data) => {
updateDashboard(data);
});
sse.on('connected', () => {
updateStatus('connected');
});
sse.on('reconnecting', () => {
updateStatus('reconnecting');
});
// Manual recovery reset
document.getElementById('refresh-btn').onclick = () => {
sse.clearRecovery();
sse.close();
window.location.reload();
};
Expected output: Client persists last event ID in localStorage, enabling recovery across page reloads.
Example 3: Ordered Event Delivery with IDs
// Server with ordered event delivery
const http = require('http');
const { EventEmitter } = require('events');
class OrderedSSEServer {
constructor() {
this.emitter = new EventEmitter();
this.sequenceMap = new Map(); // connection -> lastSeq
this.globalSeq = 0;
}
handleConnection(req, res) {
const lastEventId = parseInt(
req.headers['last-event-id']?.replace('seq-', '')
) || 0;
const connId = `${Date.now()}-${Math.random()}`;
this.sequenceMap.set(connId, lastEventId);
res.writeHead(200, {
'Content-Type': 'text/event-stream',
'Cache-Control': 'no-cache',
});
// Send missed events from buffer
const buffer = this.getEventBuffer(lastEventId);
buffer.forEach(entry => {
res.write(
`id: seq-${entry.seq}\n` +
`event: ${entry.event}\n` +
`data: ${JSON.stringify(entry.data)}\n` +
'\n'
);
});
// Subscribe to new events with ordering
const listener = (eventData) => {
const currentSeq = this.sequenceMap.get(connId) || 0;
const nextSeq = currentSeq + 1;
const message =
`id: seq-${nextSeq}\n` +
`event: ${eventData.event}\n` +
`data: ${JSON.stringify(eventData.data)}\n` +
'\n';
res.write(message);
this.sequenceMap.set(connId, nextSeq);
};
this.emitter.on('event', listener);
req.on('close', () => {
this.emitter.off('event', listener);
this.sequenceMap.delete(connId);
});
}
broadcast(event, data) {
this.globalSeq++;
const eventData = { seq: this.globalSeq, event, data };
this.eventBuffer.push(eventData);
if (this.eventBuffer.length > 1000) {
this.eventBuffer.shift();
}
this.emitter.emit('event', eventData);
}
getEventBuffer(sinceSeq) {
return this.eventBuffer.filter(e => e.seq > sinceSeq);
}
}
const sseServer = new OrderedSSEServer();
http.createServer((req, res) => sseServer.handleConnection(req, res))
.listen(3000);
// Broadcast events from anywhere
setInterval(() => {
sseServer.broadcast('update', {
value: Math.random(),
time: Date.now(),
});
}, 1000);
Expected output: Server ensures ordered delivery with sequence numbers, replaying missed events in the correct order.
Common Mistakes
| Mistake | Explanation |
|---|---|
| Not sending IDs with events | Without IDs, Last-Event-ID is empty, and the client cannot resume from a specific point |
| Using non-sequential IDs | IDs should be monotonically increasing to enable proper resume logic |
| Not persisting IDs on the client | Loss of lastEventId on page reload prevents recovery |
| Assuming Last-Event-ID is always set | On first connection, Last-Event-ID is empty |
| Sending duplicate events on replay | Ensure idempotent event handling or deduplicate on the client |
Practice Questions
- How does the server receive the last event ID?
- How do you generate useful event IDs?
- How does the client access the last event ID?
- How do you implement event replay on the server?
- How do you persist the last event ID across page reloads?
Challenge
Build a reliable SSE system for a notification service. Events must be delivered exactly once, in order, with no data loss on reconnection. Include server-side event buffering with configurable retention, and client-side deduplication.
FAQ
Mini Project
Build a reliable event streaming system for a stock ticker. The server buffers the last 10,000 events with sequential IDs. Clients recover missed events on reconnection. Include a status indicator showing the last received event ID and any gaps in delivery.
What's Next
Learn about CORS configuration for SSE
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro