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.

Broadcast events are usually queued

ShouldBroadcast normally sends the broadcast through the queue, which prevents a slow network call to the broadcast backend from extending the original HTTP request. That also means "the event fired" and "the browser received it" are separated by a queue worker. If broadcasting appears to do nothing, check the queue connection and worker before debugging Echo.

For the relatively rare case where broadcasting must happen synchronously, Laravel provides a separate immediate-broadcast contract; use it deliberately because it puts the broadcast latency back on the request path.

Control the payload instead of broadcasting an entire model by accident

A public model property on the event can serialize more data than the frontend needs. Define a compact broadcast payload when the event contains sensitive or heavy model state:

public function broadcastWith(): array
{
    return [
        'order_id' => $this->order->id,
        'status' => $this->order->status,
    ];
}

This also makes the JavaScript contract stable if the Eloquent model later gains attributes or relationships.

Private-channel authorization is not message authorization

The channel callback decides who may subscribe. The server-side code that fires an event still needs normal application authorization around the action that changes the order, sends the chat message, or creates the notification. A private WebSocket channel prevents unauthorized listeners; it does not validate the HTTP request that caused the event.

Use presence channels only when membership itself is part of the feature

A presence channel adds authenticated membership information on top of a private channel — useful for "who is online" or collaborative UI. It is unnecessary overhead for a one-way order-status notification. Choose the simplest channel type that matches the information the client actually needs.

Topics: APIs & Integrations