Laravel Reverb: Real-Time Features Without Paying for Pusher
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.