Sending Server Sent Events (SSE) with Laravel

Dan Pastori

December 9th, 2025

I've been hard at work developing BenchKit, a benchmarking suite to help you get some metrics on our Docker images in different configurations. One of the goals of this project is to combine everything a single Docker PHP container. Simple, right? Well we also need real-time feedback from our benchmarking scripts in the web UI without a separate container to run a WebSocket server. That leads us to using server-sent events (SSE).

What Are Server Sent Events?

Simply put, Server Sent Events are a 1 way event stream from the server to the client. Essentially a WebSocket, but with a few limitations. They are the perfect usecase for what we need. While tools like Laravel Reverb allow you to scale, help with auth, and overall much better to use for larger scale applications, we just want live stream data from our server to a single user.

EventSource And Streaming Responses

There are two sides to handle a server-sent event, setting up the "EventSource" on the client through JavaScript and streaming a response from Laravel. I won't go through the entire set up of the EventSource, because Mozilla provides an excellent tutorial on using server-sent events. They even include a PHP example!

However, on the Laravel side, you have some extra tools at your disposal.

Responding to a Route with a Stream

What's cool about Laravel, is the framework already has a nice way to stream responses back to the client, built in. Normally, when responding from a controller route, you'd return a page view, some JSON from an API, or a redirect after creating data. Within the framework is also a way to stream a response. You'd use this:

Simple streaming response from Laravel

return response()->stream(function (){
    // Return your event here!
},  200, [
    'Content-Type' => 'text/event-stream',
    'Cache-Control' => 'no-cache',
    'Connection' => 'keep-alive',
    'X-Accel-Buffering' => 'no',
    'Access-Control-Allow-Origin' => '*',
]);

The big thing to note, is you want to set the Content-Type to text/event-stream. THis is how the EventSource will handle picking it up.

Within this response stream, you will be printing or echo-ing out data. It feels kind of raw to do this, but here's how it's set up:

Send an event from Laravel to the EventSource in the client.

return response()->stream(function (){
    while (ob_get_level()) {
        ob_end_flush();
    }
    @ini_set('output_buffering', 'off');
    @ini_set('zlib.output_compression', '0');
    set_time_limit(0);

    echo "retry: 2000\n\n"; // keep connection healthy
    @ob_flush(); flush();

    echo "data: " . json_encode([
        'timestamp' => date('Y-m-d H:i:s'),
        'data' => 'Your Data'
    ]) . "\n\n";
    @ob_flush(); flush();

})

When sending a server-sent event, make sure you echo out "data: " before anything else. In our case, we echo JSON, but, it could be a single line of text too.

One quick gotcha that I ran into with BenchKit was we had longer running processes that didn't send anything back to the EventSource. This lead to a timeout on the JavaScript EventSource side. When running Geekbench, it could be a minute or so before anything was printed to the console.

If you have a longer running process, the best way to handle this is to send a "heartbeat" event. This simple event will keep your EventSource alive and looking for more content.

For example, we use the \Symfony\Component\Process\Process class. We can then, while the process is running, check the last time we sent any data and send a heartbeat event. We don't have to process this at all on the JavaScript side, it just keeps the EventSource connected.

Example of sending a heartbeat event to keep the EventSource alive

$lastHeartbeat = time();
//... Set up code for process

while( $process->isRunning() ){
    //... Run the long process

    // Send heartbeat every 30 seconds
    if (time() - $lastHeartbeat >= 30) {
        echo "data: " . json_encode([
            'timestamp' => date('Y-m-d H:i:s'),
            'type' => 'heartbeat',
            'output' => 'Connection alive',
        ]) . "\n\n";
        @ob_flush(); flush();
        $lastHeartbeat = time();
    }
                
}

When to use Server Sent Events?

I'd recommend using a Server Sent Event for small pieces of non-important data or in an app that's extremely tiny. It's amazing to have this capability to live-stream data from the server to the client without any additional libraries or servers. However, I'd recommend using Reverb or another WebSocket implementation for more sensitive data or data easier to scale.

That's not to say Server Sent Events are worthless, they aren't. They serve our purpose perfectly and I'm sure I could find other places for them too.

If you want to check out our full implementation, check out the BenchKit repo. It's 100% open-source. Even better, feel free to run BenchKit and submit your benchmarks!

Want to work together?

Professional developers choose Server Side Up to ship quality applications without surrendering control. Explore our tools and resources or work directly with us.

Join our community

We're a community of 3,000+ members help each other level up our development skills.

Platinum Sponsors

Active Discord Members

We help each other through the challenges and share our knowledge when we learn something cool.

Stars on GitHub

Our community is active and growing.

Newsletter Subscribers

We send periodic updates what we're learning and what new tools are available. No spam. No BS.

Sign up for our newsletter

Be the first to know about our latest releases and product updates.

    Privacy first. No spam. No sharing. Just updates.