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
. Use php 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 serves wss://
directly.
Wire up the frontend with Laravel Echo
Reverb uses the Pusher protocol, so the browser uses pusher-js
under the hood with Echo’s reverb
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 = 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'],
})
`
The reverb
broadcaster requires laravel-echo
v1.16.0+.
Build assets:
npm run build # or npm run dev
Broadcasting 101 (server side)
Create a broadcastable event
`
<?php
namespace 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 custom broadcastAs
.
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. Check
queue:work
logs. - CORS/Origin errors.
Your frontend origin isn’t in
allowed_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 set
broadcastAs()
, 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, run reverb: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.
Top comments (0)