Laravel

WebSockets & Broadcasting

Laravel Broadcasting lets you broadcast server-side events over WebSockets to your frontend in real time, using drivers like Pusher, Ably, or the self-hosted Laravel WebSockets package.

Broadcasting Config: Set the broadcast driver in .env and configure credentials. The pusher driver is the most common choice.
📄.env
ENV
BROADCAST_DRIVER=pusher

PUSHER_APP_ID=your-app-id
PUSHER_APP_KEY=your-app-key
PUSHER_APP_SECRET=your-app-secret
PUSHER_APP_CLUSTER=mt1

# For self-hosted laravel-websockets package
PUSHER_HOST=127.0.0.1
PUSHER_PORT=6001
PUSHER_SCHEME=http
Enable Broadcasting: Uncomment the BroadcastServiceProvider in config/app.php and install the Pusher PHP SDK.
📄terminal
BASH
composer require pusher/pusher-php-server

# If using laravel-websockets (self-hosted alternative)
composer require beyondcode/laravel-websockets
php artisan vendor:publish --provider="BeyondCode\LaravelWebSockets\WebSocketsServiceProvider" --tag="migrations"
php artisan migrate
php artisan websockets:serve
Creating a Broadcast Event: Generate an event class and implement ShouldBroadcast. Define the channel and data payload in broadcastOn() and broadcastWith().
📄terminal
BASH
php artisan make:event OrderShipped
📄app/Events/OrderShipped.php
PHP
<?php

namespace App\Events;

use App\Models\Order;
use Illuminate\Broadcasting\Channel;
use Illuminate\Broadcasting\PrivateChannel;
use Illuminate\Contracts\Broadcasting\ShouldBroadcast;
use Illuminate\Foundation\Events\Dispatchable;
use Illuminate\Queue\SerializesModels;

class OrderShipped implements ShouldBroadcast
{
    use Dispatchable, SerializesModels;

    public function __construct(public Order $order) {}

    // Channel to broadcast on
    public function broadcastOn(): array
    {
        return [
            new PrivateChannel("orders.{$this->order->user_id}"),
        ];
    }

    // Data sent to the frontend
    public function broadcastWith(): array
    {
        return [
            'order_id'   => $this->order->id,
            'status'     => $this->order->status,
            'updated_at' => $this->order->updated_at->toDateTimeString(),
        ];
    }

    // Custom event name (default: class name)
    public function broadcastAs(): string
    {
        return 'order.shipped';
    }
}
Firing the Event: Broadcast an event from anywhere in your application — controllers, jobs, listeners, etc.
📄app/Http/Controllers/OrderController.php
PHP
use App\Events\OrderShipped;

public function ship(Order $order): JsonResponse
{
    $order->update(['status' => 'shipped']);

    // Fire the broadcast event
    broadcast(new OrderShipped($order));

    // Or: only broadcast to others (exclude current request)
    broadcast(new OrderShipped($order))->toOthers();

    return response()->json(['message' => 'Order shipped']);
}
Channel Routes: Define channel authorization in routes/channels.php. Private and presence channels require user authorization callbacks.
📄routes/channels.php
PHP
<?php

use Illuminate\Support\Facades\Broadcast;

// Public channel — no auth needed (anyone can subscribe)
Broadcast::channel('announcements', function () {
    return true;
});

// Private channel — return true/false to allow/deny
Broadcast::channel('orders.{userId}', function ($user, $userId) {
    return (int) $user->id === (int) $userId;
});

// Presence channel — return array of user info to allow
Broadcast::channel('chat.{roomId}', function ($user, $roomId) {
    if ($user->canJoinRoom($roomId)) {
        return ['id' => $user->id, 'name' => $user->name];
    }
});
Pusher Setup (config): Ensure config/broadcasting.php has the Pusher options correctly set, including useTLS for production.
📄config/broadcasting.php (pusher section)
PHP
'pusher' => [
    'driver'  => 'pusher',
    'key'     => env('PUSHER_APP_KEY'),
    'secret'  => env('PUSHER_APP_SECRET'),
    'app_id'  => env('PUSHER_APP_ID'),
    'options' => [
        'cluster'  => env('PUSHER_APP_CLUSTER'),
        'useTLS'   => true,
        // Self-hosted laravel-websockets overrides:
        'host'     => env('PUSHER_HOST', '127.0.0.1'),
        'port'     => env('PUSHER_PORT', 6001),
        'scheme'   => env('PUSHER_SCHEME', 'http'),
        'encrypted' => true,
        'curl_options' => [
            CURLOPT_SSL_VERIFYHOST => 0,
            CURLOPT_SSL_VERIFYPEER => 0,
        ],
    ],
],
Install Laravel Echo & Pusher JS: On the frontend, install Echo and the Pusher JS client, then configure Echo in your JavaScript bootstrap file.
📄terminal – npm
BASH
npm install --save-dev laravel-echo pusher-js
📄resources/js/bootstrap.js
JS
import Echo from 'laravel-echo';
import Pusher from 'pusher-js';

window.Pusher = Pusher;

window.Echo = new Echo({
    broadcaster:  'pusher',
    key:          import.meta.env.VITE_PUSHER_APP_KEY,
    cluster:      import.meta.env.VITE_PUSHER_APP_CLUSTER,
    forceTLS:     true,

    // For self-hosted laravel-websockets:
    // wsHost:    import.meta.env.VITE_PUSHER_HOST,
    // wsPort:    import.meta.env.VITE_PUSHER_PORT,
    // wssPort:   import.meta.env.VITE_PUSHER_PORT,
    // forceTLS:  false,
    // enabledTransports: ['ws', 'wss'],
});
Listening to Events on Frontend: Use Echo.channel() for public, .private() for private, and .join() for presence channels.
📄resources/js/app.js
JS
// Listen on a public channel
window.Echo.channel('announcements')
    .listen('.announcement.created', (event) => {
        console.log('New announcement:', event);
    });

// Listen on a private channel (requires auth)
window.Echo.private(`orders.${userId}`)
    .listen('.order.shipped', (event) => {
        console.log('Order shipped:', event.order_id, event.status);
        showNotification(`Your order #${event.order_id} has been shipped!`);
    });

// Listen on a presence channel (see who is online)
window.Echo.join(`chat.${roomId}`)
    .here((users) => {
        console.log('Users in channel:', users);
    })
    .joining((user) => {
        console.log(user.name, 'joined');
    })
    .leaving((user) => {
        console.log(user.name, 'left');
    })
    .listen('.MessageSent', (event) => {
        console.log('Message:', event.message);
    });

// Stop listening / leave channel
window.Echo.leave(`orders.${userId}`);