Server-Sent Events in ASP.NET Core Minimal APIs

You need to push real-time updates from server to browser. SignalR is the default answer, but sometimes you don’t need bidirectional communication. You just need the server to stream events to the client: a progress bar, a live feed, a notification stream.

Server-Sent Events (SSE) does exactly that. It’s a native browser API built on plain HTTP. No WebSocket upgrade, no SignalR hub, no client library.

The Basics

Set the content type to text/event-stream and write data: lines followed by double newlines:

app.MapGet("/events/progress", async (HttpContext context, CancellationToken ct) =>
{
context.Response.ContentType = "text/event-stream";
context.Response.Headers.CacheControl = "no-cache";
string[] steps = ["Validating", "Processing", "Confirming"];
for (int i = 0; i < steps.Length; i++)
{
var json = JsonSerializer.Serialize(new { step = steps[i], percent = (i + 1) * 33 });
await context.Response.WriteAsync($"data: {json}\n\n", ct);
await context.Response.Body.FlushAsync(ct);
await Task.Delay(1500, ct);
}
});

The CancellationToken fires when the client disconnects, so you stop doing work immediately.

Named Events

Add an event: line to categorize messages:

await context.Response.WriteAsync($"event: progress\ndata: {json}\n\n", ct);

The client can then listen for specific event types.

The Client Side

The browser’s native EventSource API handles connection and auto-reconnection:

const source = new EventSource('/events/progress');
source.addEventListener('progress', (e) => {
const data = JSON.parse(e.data);
updateProgressBar(data.percent);
});
source.onerror = () => console.log('Reconnecting...');

Auto-reconnect is built into the spec. You get resilience for free.

Streaming with Channels

For fan-out scenarios like live dashboards, pair SSE with System.Threading.Channels:

app.MapGet("/events/orders", async (HttpContext ctx, OrderEventBus bus, CancellationToken ct) =>
{
ctx.Response.ContentType = "text/event-stream";
ctx.Response.Headers.CacheControl = "no-cache";
await foreach (var evt in bus.Subscribe(ct))
{
var json = JsonSerializer.Serialize(evt);
await ctx.Response.WriteAsync($"data: {json}\n\n", ct);
await ctx.Response.Body.FlushAsync(ct);
}
});

POST events into the channel from elsewhere, and every connected SSE client receives them in real time.

SSE vs. SignalR

SSESignalR
DirectionServer to client onlyBidirectional
TransportPlain HTTPWebSocket (with fallbacks)
ClientNative EventSourceRequires SignalR library
Auto-reconnectBuilt into the specBuilt into the client
Binary dataText onlyText and binary

Choose SSE when you only need server-to-client streaming and want the simplest possible setup. Choose SignalR when you need bidirectional messaging, binary data, or group management.

Key Takeaway

SSE is the lightest way to push real-time updates from ASP.NET Core to a browser. Set text/event-stream, write data: lines, flush, and the browser handles the rest, including automatic reconnection.