Here’s the sarcastic summary with hashtags:
Modern customers anticipate instant updates: new chat messages that don’t need to be refreshed, dashboards that keep up with reality, and notifications that appear when anything changes. Real-time UX is no longer a “nice to have”; it is required for SaaS, markets, analytics, collaboration tools, and other applications. Laravel Reverb is the perfect solution for any Laravel app that needs a first-party, high-performance WebSocket server, with built-in support for Laravel Echo. With Reverb, you can spin up a WebSocket server, broadcast Laravel events, and consume them in the browser with Laravel Echo for no vendor lock-in, no per-message fees, and tight Laravel integration. Reverb also supports remote origins, Queues, and SSL. Here’s a step-by-step guide on how to set up a real-time API using Laravel Reverb:
- Install Reverb and Echo: <
Modern customers anticipate instant updates—new chat messages that don’t need to be refreshed, dashboards that keep up with reality, and notifications that appear when anything changes. Real-time UX is no longer a “nice to have”; it is required for SaaS, markets, analytics, collaboration tools, and other applications.
Laravel Reverb integrates a first-party, high-performance WebSocket server into your Laravel application. It enables you to broadcast Laravel events via WebSockets and consume them in the browser using Laravel Echo—no vendor lock-in, no per-message fees, and tight framework integration.
Below is a simple, beginner-friendly guide to creating real-time APIs in Laravel using Reverb, including setup, code samples for notifications, chat, and dashboards, recommended practices, and a direct comparison to Pusher/Echo.
What we’ll build (and why)
By the end, you’ll be able to:
- Spin up Reverb and connect a frontend via Laravel Echo.
- Broadcast server events (orders updating, metrics changing).
- Push live
notifications
, arealtime chat
, and alive dashboard
.- Avoid common pitfalls (origins, queues, SSL, scaling).
Prerequisites
- Laravel 11/12 project
- Node + Vite (default in new Laravel apps)
- Redis (recommended for scaling later)
Install & configure Laravel Reverb
Quick install
php artisan install:broadcasting --reverb
This scaffolds broadcasting, installs Reverb + Echo, and injects sensible
.env
variables.
Prefer manual?
composer require laravel/reverb && php artisan reverb:install
also works.
PHP is still highly relevant for building scalable, dynamic apps—see why PHP is a top choice for e-commerce development
Essential environment variables
Reverb identifies the app with credentials and needs host/port info:
BROADCAST_CONNECTION=reverb REVERB_APP_ID=my-app-id REVERB_APP_KEY=my-app-key REVERB_APP_SECRET=my-app-secret - Where Laravel sends broadcast messages (public entrypoint or proxy) REVERB_HOST=ws.example.com REVERB_PORT=443 REVERB_SCHEME=https - Where the Reverb server actually binds (internal) REVERB_SERVER_HOST=0.0.0.0 REVERB_SERVER_PORT=8080
Important distinction: –
REVERB_SERVER_HOST/PORT
controls the Reverb process binding;REVERB_HOST/PORT
tells Laravel where to send messages (often your public hostname through Nginx/ALB).
Allowed Origins (CORS for sockets)
Restrict which frontends may connect:
// config/reverb.php 'apps' => [[ 'app_id' => env('REVERB_APP_ID'), 'allowed_origins' => ['https://app.example.com'], // or ['*'] during local dev ]],
Requests from other origins will be rejected.
Start Reverb
php artisan reverb:start # options: # php artisan reverb:start --host=127.0.0.1 --port=9000
The default is
0.0.0.0:8080
. Usephp artisan reverb:restart
after code changes—Reverb is a long-running process.
Using Herd/Valet locally with HTTPS? You can pass a hostname or a TLS cert so Reverb serveswss://
directly.
Wire up the frontend with Laravel Echo
Reverb uses the Pusher protocol, so the browser uses
pusher-js
under the hood with Echo’sreverb
broadcaster.
Install (already done if you ran the installer):
npm i -D laravel-echo pusher-js
Bootstrap Echo (e.g., ‘resources/js/bootstrap.js`):
`
import Echo from ‘laravel-echo’
import Pusher from ‘pusher-js’
window.Pusher = Pusherwindow.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’],
})
`The
reverb
broadcaster requireslaravel-echo
v1.16.0+.
Build assets:
npm run build # or npm run dev
Broadcasting 101 (server side)
Create a broadcastable event
`
<?phpnamespace App\Events;
use Illuminate\Broadcasting\Channel;
use Illuminate\Broadcasting\PrivateChannel; // for private channels
use Illuminate\Contracts\Broadcasting\ShouldBroadcast;
use Illuminate\Queue\SerializesModels;class OrderShipmentStatusUpdated implements ShouldBroadcast
{
use SerializesModels;public function __construct( public int $orderId, public string $status, ) {} public function broadcastOn(): Channel|array { return new PrivateChannel("orders.{$this->orderId}"); } public function broadcastAs(): string { return 'OrderShipmentStatusUpdated'; } public function broadcastWith(): array { return ['orderId' => $this->orderId, 'status' => $this->status]; }
}
`
Authorize channels
routes/channels.php
controls who can subscribe:
`
use Illuminate\Support\Facades\Broadcast;
use App\Models\User;Broadcast::channel(‘orders.{orderId}’, function (User $user, int $orderId) {
// Only owners (or staff) can listen
return $user->orders()->whereKey($orderId)->exists();
});
`
Emit the event
use App\Events\OrderShipmentStatusUpdated;
event(new OrderShipmentStatusUpdated($orderId, 'shipped'));Don’t forget your queue worker. Broadcasting is queued so it doesn’t slow down responses. Make sure a worker is running (e.g.,
php artisan queue:work
).
Receive events in the browser
orders.${orderId}
// Private channel requires auth (handled by Laravel)
Echo.private()
.listen('.OrderShipmentStatusUpdated', (e) => {
console.log(e.status) // 'shipped'
})
The leading dot
'.EventName'
is used when you set a custombroadcastAs
.
Real-time feature recipes
Live notifications
Server: use Laravel Notifications with the
broadcast
channel.
`
use Illuminate\Notifications\Notification;class NewMessageNotification extends Notification
{
public function via($notifiable): array
{
return [‘database’, ‘broadcast’];
}public function toBroadcast($notifiable): array { return ['message' => 'You have a new message!']; }
}
`Client:
App.Models.User.${userId}
Echo.private()
.notification((n) => {
// render toast/badge/center
console.log(n.message)
})
Notifications over Echo use the user’s private channel automatically.
Realtime chat (private & presence)
Authorize a presence channel to know who’s online and show “typing…”:
// routes/channels.php
Broadcast::channel('chat.{roomId}', function ($user, int $roomId) {
// return user info to other members
return ['id' => $user->id, 'name' => $user->name];
});
Frontend:
chat.${roomId}`)
// Presence channel
const channel = Echo.join(
.here(users => renderOnline(users))
.joining(user => addOnline(user))
.leaving(user => removeOnline(user))// Typing indicator using client events (no round-trip)
channel.whisper(‘typing’, { name: me.name })
channel.listenForWhisper(‘typing’, (e) => showTyping(e.name))
`(With Pusher, enabling client events in your Pusher app is required; Reverb handles whispering with Echo locally.)
Broadcast messages from the server:
`
class MessagePosted implements ShouldBroadcast
{
public function __construct(public int $roomId, public array $message) {}public function broadcastOn(): array { return [new \Illuminate\Broadcasting\PresenceChannel("chat.{$this->roomId}")]; }
}
`Listen in the browser:
chat.${roomId}
Echo.join()
.listen('MessagePosted', (e) =>
appendMessage(e.message))
Streaming dashboard metrics
Backend:
`
class MetricUpdated implements ShouldBroadcast
{
public function __construct(public string $key, public float $value) {}public function broadcastOn(): array { return [new \Illuminate\Broadcasting\Channel('metrics')]; // public }
}
`
Client:
Echo.channel('metrics')
.listen('MetricUpdated', ({ key, value }) => updateChart(key, value))
Learn more about PHP & It’s Trending Frameworks
Production checklist & best practices
Run Reverb under a process manager Supervisor/systemd) and increase
minfds
so it can open enough file descriptors for connections.
[supervisord]
minfds=10000
- Reverse proxy & SSL. Terminate TLS at Nginx/ALB and proxy to REVERB_SERVER_HOST:REVERB_SERVER_PORT. Use wss:// in production.
- Allowed origins. Lock down allowed_origins in config/reverb.php to your app domains.
- Queues are mandatory. Keep a reliable queue worker online (Horizon is great) so broadcasts are sent promptly.
- Don’t leak secrets. Treat WebSocket payloads like API responses. Never broadcast sensitive data; use private/presence channels with robust auth.
- Shape payloads intentionally. Use broadcastWith() to send just what the UI needs; use broadcastAs() to control event names.
- Restart on deploy. Reverb is long-running. Call
php artisan reverb:restart
during deployments for zero-downtime restarts.- Scale horizontally with Redis. Set
REVERB_SCALING_ENABLED=true
and run Reverb on multiple instances behind a load balancer; Reverb uses Redis pub/sub to fan out messages.
Common pitfalls (and how to fix them)
- “Connected, but no events arrive.”
The queue worker isn’t running or is failing. Checkqueue:work
logs.- CORS/Origin errors.
Your frontend origin isn’t inallowed_origins
. Add the domain or*
for local dev.- Port/host confusion.
REVERB_SERVER_*
is where the process binds;REVERB_HOST/PORT
is the public address Echo connects to.- Mixed content (WS vs WSS).
On HTTPS sites you must use secure WebSockets (wss://) or the browser will block the connection.- Event name mismatch.
If you setbroadcastAs()
, listen with a leading dot:.YourEventName
.
Reverb vs Pusher vs Echo (what’s the difference?)
Tool What it is Pros Cons Best for Laravel Reverb First-party WebSocket server you host No vendor fees; deep Laravel integration; easy install; Redis-backed horizontal scaling You manage infrastructure and uptime Apps wanting control & predictable cost Pusher Channels Managed WebSocket service Zero ops; global infrastructure; great tooling Usage-based cost; external dependency Teams prioritizing speed of launch Ably (Pusher-compatible) Managed realtime platform Global low-latency; rich features Cost; external dependency High-scale global apps Laravel Echo Client library, not a server Unified API in JS (works with Reverb/Pusher/Ably) N/A—used with one of the above Every Laravel frontend Reverb and Pusher/Ably are server/broker choices. Echo is the browser client you’ll use with any of them. Reverb uses the Pusher protocol, so Echo’s Pusher adapter works seamlessly with the
reverb
broadcaster.
Advanced: presence, client events, and model broadcasting
- Presence channels return member info in the channel auth callback and let you show “who’s online” and online counts.
- Client events (aka “whispers”) are great for ephemeral UX like typing indicators—sent peer-to-peer via the channel without hitting your Laravel backend.
- Model broadcasting lets you stream changes to Eloquent models with Echo hooks (useEchoModel) for a delightful DX in React/Vue.
Horizontal scaling in a sentence
Flip
REVERB_SCALING_ENABLED=true
, point all Reverb instances at the same Redis, runreverb:start
on multiple servers, put them behind a load balancer. Done.
Conclusion
Laravel Reverb makes real-time APIs feel like Laravel: events you already use become live updates in the browser; Echo provides a clean, chainable API; and you can self-host or scale out with Redis as demand grows. With the patterns mentioned above—notifications, chat, and live dashboards—you can provide the kind of UX that people increasingly expect.