tests.ws

Laravel WebSocket Guide

laravel php websocket reverb broadcasting real-time

Laravel WebSocket support lets you build real-time applications with event broadcasting, live notifications, and instant updates. The framework provides a unified broadcasting API that works with multiple drivers including Laravel Reverb (the official first-party solution), Pusher, and the self-hosted beyondcode/laravel-websockets package. This guide covers everything you need to implement websocket in laravel applications.

Laravel Broadcasting Overview

Laravel’s broadcasting system abstracts WebSocket communication into a simple, expressive API. Instead of writing low-level WebSocket code, you broadcast events from your server and listen for them on the client using Laravel Echo.

The broadcasting architecture consists of three main components:

  1. Server-side events that you broadcast from your Laravel application
  2. Broadcasting drivers that handle the WebSocket connection (Reverb, Pusher, or self-hosted)
  3. Client-side Echo that listens for events in your JavaScript

Configure broadcasting in config/broadcasting.php:

<?php

return [
    'default' => env('BROADCAST_DRIVER', 'reverb'),

    'connections' => [
        'reverb' => [
            'driver' => 'reverb',
            'key' => env('REVERB_APP_KEY'),
            'secret' => env('REVERB_APP_SECRET'),
            'app_id' => env('REVERB_APP_ID'),
            'options' => [
                'host' => env('REVERB_HOST'),
                'port' => env('REVERB_PORT', 8080),
                'scheme' => env('REVERB_SCHEME', 'http'),
            ],
        ],

        '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'),
                'host' => env('PUSHER_HOST'),
                'port' => env('PUSHER_PORT'),
                'scheme' => env('PUSHER_SCHEME'),
                'encrypted' => true,
                'useTLS' => env('PUSHER_SCHEME', 'https') === 'https',
            ],
        ],
    ],
];

Enable broadcasting by uncommenting BroadcastServiceProvider in config/app.php:

<?php

return [
    'providers' => [
        // ...
        App\Providers\BroadcastServiceProvider::class,
    ],
];

Laravel Reverb

Laravel Reverb is the official first-party WebSocket server introduced in Laravel 11. It provides a blazing-fast, scalable WebSocket solution built specifically for Laravel applications. Reverb eliminates the need for third-party services and gives you full control over your WebSocket infrastructure.

Install Reverb using Composer:

composer require laravel/reverb

Publish the configuration file:

php artisan reverb:install

This creates a config/reverb.php file and adds the necessary environment variables to your .env:

BROADCAST_DRIVER=reverb
REVERB_APP_ID=my-app-id
REVERB_APP_KEY=my-app-key
REVERB_APP_SECRET=my-app-secret
REVERB_HOST=localhost
REVERB_PORT=8080
REVERB_SCHEME=http

VITE_REVERB_APP_KEY="${REVERB_APP_KEY}"
VITE_REVERB_HOST="${REVERB_HOST}"
VITE_REVERB_PORT="${REVERB_PORT}"
VITE_REVERB_SCHEME="${REVERB_SCHEME}"

Start the Reverb server:

php artisan reverb:start

For production, run Reverb with supervisor or as a daemon:

php artisan reverb:start --host=0.0.0.0 --port=8080

Reverb supports horizontal scaling with Redis. Configure the scaling driver in config/reverb.php:

<?php

return [
    'scaling' => [
        'enabled' => env('REVERB_SCALING_ENABLED', false),
        'channel' => env('REVERB_SCALING_CHANNEL', 'reverb'),
    ],

    'pulse_ingest_interval' => env('REVERB_PULSE_INGEST_INTERVAL', 15),
    'telescope_ingest_interval' => env('REVERB_TELESCOPE_INGEST_INTERVAL', 15),
];

Enable Redis scaling in production:

REVERB_SCALING_ENABLED=true
REDIS_HOST=127.0.0.1
REDIS_PASSWORD=null
REDIS_PORT=6379

Pusher Driver

Pusher is a hosted WebSocket service that works seamlessly with Laravel broadcasting. It handles the infrastructure so you can focus on building features. The Pusher driver is ideal for applications that need reliable WebSocket support without managing servers.

Install the Pusher PHP SDK:

composer require pusher/pusher-php-server

Configure Pusher credentials in .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

VITE_PUSHER_APP_KEY="${PUSHER_APP_KEY}"
VITE_PUSHER_APP_CLUSTER="${PUSHER_APP_CLUSTER}"

The Pusher configuration in config/broadcasting.php supports custom hosts for Pusher-compatible services:

'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'),
        'host' => env('PUSHER_HOST', 'api-'.env('PUSHER_APP_CLUSTER').'.pusher.com'),
        'port' => env('PUSHER_PORT', 443),
        'scheme' => env('PUSHER_SCHEME', 'https'),
        'encrypted' => true,
        'useTLS' => true,
    ],
    'client_options' => [
        'timeout' => 60,
    ],
],

beyondcode/laravel-websockets

The beyondcode laravel websockets package provides a self-hosted, Pusher-compatible WebSocket server. It’s a drop-in replacement for Pusher that runs on your own infrastructure, eliminating monthly costs for WebSocket services.

Install the package:

composer require beyondcode/laravel-websockets

Publish the configuration and migrations:

php artisan vendor:publish --provider="BeyondCode\LaravelWebSockets\WebSocketsServiceProvider" --tag="migrations"
php artisan migrate
php artisan vendor:publish --provider="BeyondCode\LaravelWebSockets\WebSocketsServiceProvider" --tag="config"

Configure your WebSocket server in config/websockets.php:

<?php

return [
    'dashboard' => [
        'port' => env('LARAVEL_WEBSOCKETS_PORT', 6001),
    ],

    'apps' => [
        [
            'id' => env('PUSHER_APP_ID'),
            'name' => env('APP_NAME'),
            'key' => env('PUSHER_APP_KEY'),
            'secret' => env('PUSHER_APP_SECRET'),
            'path' => env('PUSHER_APP_PATH'),
            'capacity' => null,
            'enable_client_messages' => false,
            'enable_statistics' => true,
        ],
    ],

    'ssl' => [
        'local_cert' => env('LARAVEL_WEBSOCKETS_SSL_LOCAL_CERT', null),
        'local_pk' => env('LARAVEL_WEBSOCKETS_SSL_LOCAL_PK', null),
        'passphrase' => env('LARAVEL_WEBSOCKETS_SSL_PASSPHRASE', null),
    ],

    'channelManager' => \BeyondCode\LaravelWebSockets\ChannelManagers\ArrayChannelManager::class,
];

Update your .env to use the WebSocket server as a Pusher replacement:

BROADCAST_DRIVER=pusher
PUSHER_APP_ID=local
PUSHER_APP_KEY=local-key
PUSHER_APP_SECRET=local-secret
PUSHER_HOST=127.0.0.1
PUSHER_PORT=6001
PUSHER_SCHEME=http

VITE_PUSHER_APP_KEY="${PUSHER_APP_KEY}"
VITE_PUSHER_HOST="${PUSHER_HOST}"
VITE_PUSHER_PORT="${PUSHER_PORT}"
VITE_PUSHER_SCHEME="${PUSHER_SCHEME}"

Start the WebSocket server:

php artisan websockets:serve

Access the debug dashboard at http://localhost:8000/laravel-websockets to monitor connections and events in real time.

For production deployments, run the server with supervisor and enable SSL in the configuration. Use Redis as the channel manager for horizontal scaling:

'channelManager' => \BeyondCode\LaravelWebSockets\ChannelManagers\RedisChannelManager::class,

Laravel Echo Client Setup

Laravel Echo is the JavaScript library that connects to your WebSocket server and listens for broadcast events. It provides a clean API that works with any broadcasting driver.

Install Echo and the Pusher JavaScript library (required even for Reverb and laravel-websockets):

npm install --save-dev laravel-echo pusher-js

Configure Echo in your resources/js/bootstrap.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 ?? 'mt1',
    wsHost: import.meta.env.VITE_PUSHER_HOST ?? `ws-${import.meta.env.VITE_PUSHER_APP_CLUSTER}.pusher.com`,
    wsPort: import.meta.env.VITE_PUSHER_PORT ?? 80,
    wssPort: import.meta.env.VITE_PUSHER_PORT ?? 443,
    forceTLS: (import.meta.env.VITE_PUSHER_SCHEME ?? 'https') === 'https',
    enabledTransports: ['ws', 'wss'],
});

For Laravel Reverb, use the reverb broadcaster:

import Echo from 'laravel-echo';
import Pusher from 'pusher-js';

window.Pusher = Pusher;

window.Echo = new Echo({
    broadcaster: 'reverb',
    key: import.meta.env.VITE_REVERB_APP_KEY,
    wsHost: import.meta.env.VITE_REVERB_HOST,
    wsPort: import.meta.env.VITE_REVERB_PORT ?? 80,
    wssPort: import.meta.env.VITE_REVERB_PORT ?? 443,
    forceTLS: (import.meta.env.VITE_REVERB_SCHEME ?? 'https') === 'https',
    enabledTransports: ['ws', 'wss'],
});

If you use socket io laravel instead, install the Socket.IO client:

npm install --save-dev socket.io-client

Configure Echo for Socket.IO:

import Echo from 'laravel-echo';
import io from 'socket.io-client';

window.io = io;

window.Echo = new Echo({
    broadcaster: 'socket.io',
    host: window.location.hostname + ':6001',
});

Channels: Public, Private, and Presence

Laravel broadcasting supports three channel types: public channels for unrestricted access, private channels for authenticated users, and presence channels that track who’s listening.

Public Channels

Public channels allow any client to listen without authentication. Create a broadcast event:

<?php

namespace App\Events;

use Illuminate\Broadcasting\Channel;
use Illuminate\Broadcasting\InteractsWithSockets;
use Illuminate\Contracts\Broadcasting\ShouldBroadcast;
use Illuminate\Foundation\Events\Dispatchable;
use Illuminate\Queue\SerializesModels;

class ServerMetricsUpdated implements ShouldBroadcast
{
    use Dispatchable, InteractsWithSockets, SerializesModels;

    public function __construct(
        public array $metrics
    ) {}

    public function broadcastOn(): Channel
    {
        return new Channel('server-metrics');
    }

    public function broadcastAs(): string
    {
        return 'metrics.updated';
    }
}

Listen on the client:

Echo.channel('server-metrics')
    .listen('.metrics.updated', (e) => {
        console.log('Metrics:', e.metrics);
    });

Private Channels

Private channels require authorization. Define authorization logic in routes/channels.php:

<?php

use Illuminate\Support\Facades\Broadcast;

Broadcast::channel('orders.{orderId}', function ($user, $orderId) {
    return $user->orders()->where('id', $orderId)->exists();
});

Broadcast::channel('team.{teamId}', function ($user, $teamId) {
    return $user->teams()->where('id', $teamId)->exists();
});

Create a private channel event:

<?php

namespace App\Events;

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

class OrderShipped implements ShouldBroadcast
{
    use Dispatchable, InteractsWithSockets, SerializesModels;

    public function __construct(
        public Order $order
    ) {}

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

Listen on authenticated clients:

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

Presence Channels

Presence channels extend private channels with member tracking. They let you see who else is subscribed to the channel.

Define presence channel authorization in routes/channels.php:

<?php

use Illuminate\Support\Facades\Broadcast;

Broadcast::channel('chat.{roomId}', function ($user, $roomId) {
    if ($user->canAccessRoom($roomId)) {
        return [
            'id' => $user->id,
            'name' => $user->name,
            'avatar' => $user->avatar_url,
        ];
    }
});

Create a presence channel event:

<?php

namespace App\Events;

use Illuminate\Broadcasting\InteractsWithSockets;
use Illuminate\Broadcasting\PresenceChannel;
use Illuminate\Contracts\Broadcasting\ShouldBroadcast;
use Illuminate\Foundation\Events\Dispatchable;
use Illuminate\Queue\SerializesModels;

class MessageSent implements ShouldBroadcast
{
    use Dispatchable, InteractsWithSockets, SerializesModels;

    public function __construct(
        public string $roomId,
        public string $message,
        public array $user
    ) {}

    public function broadcastOn(): PresenceChannel
    {
        return new PresenceChannel('chat.' . $this->roomId);
    }
}

Listen and track members on the client:

Echo.join('chat.' + roomId)
    .here((users) => {
        console.log('Currently in room:', users);
    })
    .joining((user) => {
        console.log(user.name + ' joined');
    })
    .leaving((user) => {
        console.log(user.name + ' left');
    })
    .listen('MessageSent', (e) => {
        console.log('Message from ' + e.user.name + ':', e.message);
    });

Events and Listeners

Broadcast events from anywhere in your application using the broadcast helper or by dispatching events that implement ShouldBroadcast.

Create a notification event:

<?php

namespace App\Events;

use App\Models\User;
use Illuminate\Broadcasting\InteractsWithSockets;
use Illuminate\Broadcasting\PrivateChannel;
use Illuminate\Contracts\Broadcasting\ShouldBroadcast;
use Illuminate\Foundation\Events\Dispatchable;
use Illuminate\Queue\SerializesModels;

class UserNotification implements ShouldBroadcast
{
    use Dispatchable, InteractsWithSockets, SerializesModels;

    public function __construct(
        public User $user,
        public string $title,
        public string $body
    ) {}

    public function broadcastOn(): PrivateChannel
    {
        return new PrivateChannel('users.' . $this->user->id);
    }

    public function broadcastWith(): array
    {
        return [
            'title' => $this->title,
            'body' => $this->body,
            'timestamp' => now()->toIso8601String(),
        ];
    }
}

Dispatch the event from a controller or job:

<?php

namespace App\Http\Controllers;

use App\Events\UserNotification;
use App\Models\User;

class NotificationController extends Controller
{
    public function send(User $user)
    {
        broadcast(new UserNotification(
            $user,
            'New Message',
            'You have received a new message'
        ));

        return response()->json(['status' => 'sent']);
    }
}

Queue broadcasts for better performance using ShouldBroadcastNow for immediate delivery or keep ShouldBroadcast for queued broadcasting:

<?php

namespace App\Events;

use Illuminate\Contracts\Broadcasting\ShouldBroadcastNow;

class UrgentAlert implements ShouldBroadcastNow
{
    // Event implementation
}

Listen for events on the client with wildcard listeners:

Echo.private('users.' + userId)
    .notification((notification) => {
        console.log('Notification:', notification);
    });

// Listen to all events on a channel
Echo.private('users.' + userId)
    .listenToAll((event, data) => {
        console.log('Event:', event);
        console.log('Data:', data);
    });

Authentication

Private and presence channels require authentication. Laravel uses your existing session authentication to authorize channel access.

The /broadcasting/auth endpoint handles authorization. Make sure it’s included in your routes/web.php:

<?php

use Illuminate\Support\Facades\Route;

Broadcast::routes(['middleware' => ['web', 'auth']]);

Configure Echo to send authentication credentials:

window.Echo = new Echo({
    broadcaster: 'reverb',
    key: import.meta.env.VITE_REVERB_APP_KEY,
    wsHost: import.meta.env.VITE_REVERB_HOST,
    wsPort: import.meta.env.VITE_REVERB_PORT ?? 80,
    wssPort: import.meta.env.VITE_REVERB_PORT ?? 443,
    forceTLS: (import.meta.env.VITE_REVERB_SCHEME ?? 'https') === 'https',
    enabledTransports: ['ws', 'wss'],
    auth: {
        headers: {
            'X-CSRF-TOKEN': document.querySelector('meta[name="csrf-token"]').getAttribute('content'),
        },
    },
});

For API authentication using Sanctum, configure the auth endpoint:

window.Echo = new Echo({
    broadcaster: 'reverb',
    key: import.meta.env.VITE_REVERB_APP_KEY,
    wsHost: import.meta.env.VITE_REVERB_HOST,
    wsPort: import.meta.env.VITE_REVERB_PORT ?? 80,
    wssPort: import.meta.env.VITE_REVERB_PORT ?? 443,
    forceTLS: (import.meta.env.VITE_REVERB_SCHEME ?? 'https') === 'https',
    enabledTransports: ['ws', 'wss'],
    authEndpoint: '/api/broadcasting/auth',
    auth: {
        headers: {
            'Authorization': 'Bearer ' + apiToken,
            'Accept': 'application/json',
        },
    },
});

Define custom authorization in the broadcast service provider:

<?php

namespace App\Providers;

use Illuminate\Support\Facades\Broadcast;
use Illuminate\Support\ServiceProvider;

class BroadcastServiceProvider extends ServiceProvider
{
    public function boot(): void
    {
        Broadcast::routes();

        require base_path('routes/channels.php');
    }
}

Deployment

Deploy Laravel WebSocket applications with proper process management and SSL configuration. For production environments, use supervisor to keep your WebSocket server running.

Reverb Deployment

Create a supervisor configuration file at /etc/supervisor/conf.d/reverb.conf:

[program:reverb]
command=/usr/bin/php /var/www/html/artisan reverb:start --host=0.0.0.0 --port=8080
directory=/var/www/html
user=www-data
autostart=true
autorestart=true
redirect_stderr=true
stdout_logfile=/var/www/html/storage/logs/reverb.log

Reload supervisor:

sudo supervisorctl reread
sudo supervisorctl update
sudo supervisorctl start reverb

Configure Nginx to proxy WebSocket connections:

server {
    listen 443 ssl;
    server_name your-domain.com;

    ssl_certificate /path/to/cert.pem;
    ssl_certificate_key /path/to/key.pem;

    location / {
        proxy_pass http://localhost:8000;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "Upgrade";
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
    }

    location /reverb {
        proxy_pass http://localhost:8080;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "Upgrade";
        proxy_set_header Host $host;
        proxy_read_timeout 86400;
    }
}

beyondcode/laravel-websockets Deployment

Create a supervisor configuration at /etc/supervisor/conf.d/websockets.conf:

[program:websockets]
command=/usr/bin/php /var/www/html/artisan websockets:serve --host=0.0.0.0 --port=6001
directory=/var/www/html
user=www-data
autostart=true
autorestart=true
redirect_stderr=true
stdout_logfile=/var/www/html/storage/logs/websockets.log

Configure SSL in config/websockets.php:

'ssl' => [
    'local_cert' => '/path/to/fullchain.pem',
    'local_pk' => '/path/to/privkey.pem',
    'passphrase' => null,
],

Update your Nginx configuration to proxy WebSocket traffic:

location /ws {
    proxy_pass https://localhost:6001;
    proxy_http_version 1.1;
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection "Upgrade";
    proxy_set_header Host $host;
    proxy_read_timeout 86400;
}

For high-traffic applications, enable Redis scaling and monitor connections using the built-in dashboard. Consider implementing rate limiting and connection throttling in your authorization logic. Learn more about WebSocket testing to ensure reliability before deploying.

FAQ

What is the difference between Laravel Reverb and beyondcode/laravel-websockets?

Laravel Reverb is the official first-party WebSocket server introduced in Laravel 11. It’s built from the ground up for Laravel with better performance, native integration, and ongoing official support. The beyondcode laravel websockets package is a community solution that’s been available since Laravel 5, offering a self-hosted Pusher-compatible server. Reverb is the recommended choice for new projects, while beyondcode/laravel-websockets remains a solid option for existing applications or projects that need compatibility with older Laravel versions.

Can I use socket io with laravel instead of Pusher protocol?

Yes, you can use socket io laravel by installing a compatible broadcasting driver like socket.io-server. However, Laravel’s official broadcasting system is designed around the Pusher protocol, which Reverb, Pusher, and beyondcode/laravel-websockets all support. The Pusher protocol provides better integration with Laravel’s broadcasting features including private channels, presence channels, and authentication. For most Laravel applications, using the Pusher protocol with Reverb or beyondcode/laravel-websockets offers a better developer experience than implementing Socket.IO from scratch.

How do I handle WebSocket connections in Laravel tests?

Laravel provides testing utilities for broadcast events. Use the Event::fake() method to assert that events were broadcast without actually connecting to a WebSocket server. For integration testing, run your WebSocket server in a test environment and use JavaScript testing frameworks like Jest or Cypress to verify client-side behavior. The beyondcode/laravel-websockets package includes a testing mode that doesn’t require a running server. For more comprehensive testing strategies, see our guide on how to test websockets.

Should I queue my broadcast events?

Queue broadcast events for better application performance unless you need instant delivery. Events implementing ShouldBroadcast are automatically queued using your default queue connection. Use ShouldBroadcastNow for time-sensitive events that must broadcast immediately, like real-time chat messages or alerts. Configure separate queue workers for broadcasts to prevent blocking other jobs. Monitor queue performance and adjust the number of workers based on your broadcasting volume. For Laravel socket implementations with high message throughput, consider using Redis queues with dedicated broadcast workers.

For a deeper understanding of the underlying protocol, read our guide on what is WebSocket. If you need lower-level control over WebSocket connections in PHP, explore our PHP Ratchet guide for building custom WebSocket servers. Ensure your Laravel broadcasting implementation works correctly by following our WebSocket testing guide for comprehensive testing strategies.