Back to HTTP Headers

Last-Event-ID general request

Automatically sent by EventSource on reconnect, telling the server which Server-Sent Event the client last received so the stream can resume without gaps.

What it does

Last-Event-ID is automatically sent by the browser's EventSource API when it reconnects to a Server-Sent Events (SSE) stream after a connection drop. It tells the server exactly which event the client last successfully received, so a well-implemented server can replay any events the client missed during the disconnection rather than silently resuming from "now" and leaving a gap.

This is one of the few HTTP headers set entirely automatically by browser API behavior — you never set it manually in client code; it's populated based on the id: field of the last SSE event the browser processed before the connection dropped.

Syntax

Last-Event-ID: <event-id>

The value is whatever string was sent as the id: field on the last successfully-received SSE event:

Last-Event-ID: 12345
Last-Event-ID: evt-2026-08-16-0042

How SSE reconnection works end to end

Server sends events with IDs:

id: 100
data: {"status": "processing"}

id: 101
data: {"status": "complete"}

Connection drops after event 101. EventSource automatically attempts to reconnect (after the delay specified by the stream's retry: field, or a browser default), and includes:

GET /events HTTP/1.1
Last-Event-ID: 101

A server that supports replay can look up everything after event 101 and resume the stream from there, rather than starting fresh — the client experiences this as a seamless continuation rather than a gap in updates.

Implementing replay support

Servers need to explicitly support this — it doesn't happen automatically just because SSE is being used:

// Laravel example: reading the header and replaying missed events
$lastEventId = $request->header('Last-Event-ID');

if ($lastEventId) {
    $missedEvents = Event::where('id', '>', $lastEventId)->orderBy('id')->get();
    foreach ($missedEvents as $event) {
        echo "id: {$event->id}\n";
        echo "data: " . json_encode($event->payload) . "\n\n";
        flush();
    }
}

// Continue streaming new events as normal from here

Without this server-side logic, Last-Event-ID is simply ignored — the client still reconnects correctly, but any events generated during the disconnection window are lost rather than replayed.

Common mistakes and gotchas

Assuming replay happens automatically. EventSource sending Last-Event-ID is entirely a client-side behavior — the server has to explicitly read the header and implement replay logic, or missed events are simply gone. This is a common gap in quick SSE implementations that work fine in testing (stable connection, no drops) but silently lose data under real-world network conditions.

Not persisting events long enough to support replay. If events are only held in memory and discarded immediately after broadcast, there's nothing to replay even if you check Last-Event-ID — some form of short-term event log/buffer (a database table, a Redis stream, an in-memory ring buffer with reasonable retention) is needed to actually fulfill replay requests.

Using non-monotonic or reused event IDs. For replay logic based on "everything after this ID" to work correctly, event IDs need to be consistently ordered (typically monotonically increasing) — reusing IDs or using non-sequential identifiers makes "give me everything after X" ambiguous or impossible to implement correctly.

Forgetting this only applies to native EventSource connections. If you're implementing SSE-like behavior manually via fetch() with a readable stream (common when you need features EventSource doesn't support, like custom headers), you lose this automatic reconnection-with-Last-Event-ID behavior entirely and need to build equivalent reconnection/resume logic yourself.

Real-world examples

Initial connection, no Last-Event-ID (nothing missed yet):

GET /events HTTP/1.1
Accept: text/event-stream

Reconnection after a dropped connection:

GET /events HTTP/1.1
Accept: text/event-stream
Last-Event-ID: 4521

Server response replaying missed events, then continuing live:

HTTP/1.1 200 OK
Content-Type: text/event-stream

id: 4522
data: {"type": "missed_update"}

id: 4523
data: {"type": "missed_update"}

id: 4524
data: {"type": "live_update"}

FAQ

Do I need to set Last-Event-ID manually in my client code?

No — the browser's native EventSource API sets it automatically on reconnection attempts, based on the id: field of the last event it successfully processed. You only need to handle it server-side.

What happens if my server doesn't implement replay logic?

The reconnection still works fine at the connection level — the client successfully reconnects and resumes receiving new events. It just silently loses any events that were generated during the disconnection window, since nothing replays them.

Can I use Last-Event-ID with WebSocket instead of SSE?

No — this is specifically part of the EventSource/Server-Sent Events specification. WebSocket has no equivalent standardized reconnection-with-resume mechanism built into the browser API; you'd need to implement your own resume logic at the application-message level if building that for a WebSocket-based system.

How long should I retain events to support replay?

Depends on your application's tolerance for connection gaps — retaining events for a period covering typical network interruption durations (seconds to a few minutes, commonly) is usually sufficient; very long retention windows are rarely necessary unless your use case specifically requires supporting clients that might be disconnected for extended periods.

Fun fact

Last-Event-ID-based replay is one of the few widely-supported "resume from where you left off" mechanisms built directly into a core browser networking API rather than requiring application-level protocol design — WebSocket, by contrast, offers no equivalent out of the box, which is part of why SSE remains a genuinely compelling choice over WebSocket for one-directional server-to-client streaming use cases where reliable delivery through network hiccups matters, despite WebSocket's broader popularity for bidirectional communication.