Reader Stacks

Sending Real-Time Events With Laravel Broadcasting and WebSockets

How Laravel's broadcasting system turns a server-side event into a message a browser receives instantly — and what actually needs to run for it to work.

Sending Real-Time Events With Laravel Broadcasting and WebSockets

Broadcasting lets a server-side event — a new order, a chat message, a notification — reach a connected browser instantly, without the browser having to poll for updates. Laravel's broadcasting system is the glue between an Eloquent/application event and a WebSocket connection; it doesn't run the WebSocket server itself.

1. Make an event broadcastable

class OrderShipped implements ShouldBroadcast
{
    public function __construct(public Order $order) {}

    public function broadcastOn(): Channel
    {
        return new PrivateChannel('orders.'.$this->order->user_id);
    }
}

Implementing ShouldBroadcast (instead of just firing a normal event) is what tells Laravel this event should also go out over the configured broadcast driver, not just trigger local listeners.

2. Choose a broadcast driver

Laravel doesn't host WebSocket infrastructure itself — BROADCAST_CONNECTION in .env points to an actual driver: Pusher (hosted), Ably (hosted), or a self-hosted option like Laravel Reverb (first-party, added in Laravel 11) or soketi. The application code above is identical regardless of which driver is behind it.

3. Listen on the frontend

Echo.private('orders.' + userId)
    .listen('OrderShipped', (e) => {
        console.log('Order shipped:', e.order);
    });

Laravel Echo is the JavaScript counterpart that subscribes to the channel and receives the event — it needs to be configured with the same driver credentials as the backend.

Private channels and authorization

A PrivateChannel requires the connecting user to be authorized for that specific channel — defined in routes/channels.php:

Broadcast::channel('orders.{userId}', function ($user, $userId) {
    return (int) $user->id === (int) $userId;
});

Without this authorization callback returning true, the private channel subscription is rejected — this is the actual access-control boundary, not just a naming convention.

Firing the event

event(new OrderShipped($order));

From here it behaves like any other Laravel event — the broadcasting happens automatically because the event implements ShouldBroadcast, with no extra call needed beyond the normal event() dispatch.

Topics: APIs & Integrations