All articles
LaravelWebSocketsReverbReal-timeBroadcasting

Laravel Reverb: Real-Time Features Without Paying for Pusher

PKParth Kanasagra
8 min read

I've been building real-time features in Laravel since the Pusher integration days. Pusher is excellent but it has a payload limit (10KB per event), it costs money at scale, and it means data is routed through a third-party server. When Laravel Reverb shipped with Laravel 11, I migrated the first project over the same week.

What Reverb Actually Is

Reverb is a native WebSocket server for Laravel — a PHP process you run alongside your application that handles WebSocket connections. It speaks the same Pusher protocol, so you can use the existing Laravel Echo client without changing your frontend code. The migration from Pusher to Reverb is typically under an hour.

composer require laravel/reverb
php artisan reverb:install

Set your broadcast driver and Reverb connection details in .env:

BROADCAST_CONNECTION=reverb
REVERB_APP_ID=my-app
REVERB_APP_KEY=my-app-key
REVERB_APP_SECRET=my-app-secret
REVERB_HOST=localhost
REVERB_PORT=8080

Creating Broadcastable Events

Any Laravel event becomes a WebSocket message when it implements ShouldBroadcast:

class OrderStatusUpdated implements ShouldBroadcastNow
{
    public function __construct(
        private readonly Order $order
    ) {}

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

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

I prefer ShouldBroadcastNow over ShouldBroadcast for time-sensitive events (like order updates). The regular version queues the broadcast, which adds latency.

Echo Client Setup

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

window.Pusher = Pusher; // Echo uses the Pusher JS client internally

const 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 ?? 8080,
    forceTLS: false,
    enabledTransports: ['ws', 'wss'],
});

Listening to Private Channels

// Requires channel authentication (see BroadcastServiceProvider)
echo.private(`orders.${userId}`)
    .listen('OrderStatusUpdated', (event) => {
        updateOrderStatus(event.order_id, event.status);
        showToast(`Order is now ${event.status}`);
    });

Presence Channels: Who's Online

Presence channels let you know who else is connected. Useful for collaborative features — "3 people are viewing this document":

echo.join('document.1')
    .here((users) => setViewers(users))
    .joining((user) => addViewer(user))
    .leaving((user) => removeViewer(user))
    .listen('DocumentChanged', applyChange);

Running Reverb in Production

Use Supervisor to keep the Reverb server running and restart it on crashes:

[program:reverb]
command=php /var/www/html/artisan reverb:start
    --host=0.0.0.0
    --port=8080
    --debug
autostart=true
autorestart=true
user=www-data
stdout_logfile=/var/log/reverb.log

If you're behind Nginx, proxy WebSocket connections to Reverb:

location /app {
    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;
}

One Thing to Watch

Reverb is a long-running PHP process. If your application has a memory leak, it'll show up here first. Monitor memory usage and set --max-connections appropriately for your server size. For a typical VPS, start with a few hundred connections and scale up based on actual usage.

Related Articles

PK

Parth Kanasagra

Certified Senior Laravel & PHP Developer · 9+ years experience · Available for hire