Real-time features used to feel like a separate universe from normal Laravel work.
You'd build the app in Laravel, then wire up a third-party WebSocket service, configure broadcasting, deal with tokens, maybe add Redis, and hope the moving parts behaved. It worked, but it often felt like renting a room in someone else's building.
Laravel Reverb changes that feeling. It gives Laravel a first-party WebSocket server that integrates with broadcasting, events, channels, and Laravel Echo. You can still use external services when they make sense, but you're no longer forced there by default.
What Reverb Actually Does
Reverb handles WebSocket connections for Laravel broadcasting.
That means your server-side Laravel events can be pushed to browser clients without requiring a full page refresh or constant polling. The browser listens through Laravel Echo, and Laravel broadcasts events through the configured broadcasting driver.
Think of Reverb like a live microphone connected to your application. When something important happens, the user hears it immediately instead of waiting for the next refresh.
Common use cases include:
- Chat messages — New messages appear instantly.
- Notifications — Users see alerts without refreshing.
- Live dashboards — Metrics update as data changes.
- Collaborative tools — Multiple users see shared state changes.
- Operational screens — Admin teams see order or job status changes.
Real-time is not just "cool UI." Sometimes it reduces support tickets because users stop wondering whether anything happened.
Broadcasting Starts With Events
In Laravel, real-time broadcasting usually begins with an event.
This event implements ShouldBroadcast, which tells Laravel it should be broadcast to subscribed clients:
namespace App\Events;
use App\Models\Order;
use Illuminate\Broadcasting\PrivateChannel;
use Illuminate\Contracts\Broadcasting\ShouldBroadcast;
class OrderStatusChanged implements ShouldBroadcast
{
public function __construct(
public Order $order
) {}
public function broadcastOn(): array
{
return [
new PrivateChannel('orders.'.$this->order->id),
];
}
}
The event says: "Something happened, and clients listening to this private order channel should know."
A private channel matters because not every user should see every order. Real-time features still need authorization.
Channel Authorization Is Not Optional
WebSockets can make security mistakes feel invisible.
A normal API endpoint returns data once. A channel subscription can keep sending data over time. That means channel authorization needs the same seriousness as controller policies.
This example only allows the order owner to listen:
Broadcast::channel('orders.{orderId}', function (User $user, int $orderId) {
return $user->orders()
->whereKey($orderId)
->exists();
});
That small callback is a security boundary. Treat it like one.
Channels are like conference rooms. The door may be open technically, but only invited people should be allowed inside.
Client-Side Listening With Laravel Echo
On the frontend, Laravel Echo subscribes to the channel and listens for events.
This is the rough shape:
Echo.private(`orders.${orderId}`)
.listen('OrderStatusChanged', (event) => {
updateOrderStatus(event.order.status);
});
The browser does not need to ask every few seconds, "Did the order change?" It listens and reacts.
That's the difference between polling and real-time. Polling is checking your mailbox every minute. WebSockets are the doorbell ringing when a package arrives.
Presence Channels For "Who Is Here"
If you've built collaborative or chat features, you've probably wished you had a list of who's currently in a room. That's exactly what presence channels give you.
A presence channel is a private channel that also tracks every authenticated subscriber. The authorization callback returns the data you want others to see (not the whole user object — only what the UI needs):
Broadcast::channel('rooms.{roomId}', function (User $user, int $roomId) {
if (! $user->canEnter($roomId)) {
return null;
}
return [
'id' => $user->id,
'name' => $user->name,
'avatar' => $user->avatar_url,
];
});
On the client, Echo gives you join/leave callbacks plus a list of current members:
Echo.join(`rooms.${roomId}`)
.here((users) => renderUsers(users))
.joining((user) => addUser(user))
.leaving((user) => removeUser(user))
.listen('NewMessage', (event) => appendMessage(event.message));
Presence channels are how Google Docs avatars, Slack typing indicators, and "3 people viewing this order" badges get built. The pattern is the same: authorize, expose minimum useful data, and let the channel track membership.
One trap: presence info should never include private fields like emails or roles. Anything you put in the authorization payload is visible to every other member of the channel. Treat it as public-by-membership.
Reconnection And Missed Events
WebSocket connections drop. Laptops go to sleep, mobile devices switch networks, load balancers recycle. The question is not whether a client will disconnect — it's what happens when it reconnects.
Echo handles automatic reconnection out of the box, but events sent during the gap are lost. That's not a Reverb bug; it's how WebSocket broadcasting works. The fix is to design the UI so a missed event is recoverable.
Two patterns work well:
Refetch on reconnect. When the socket comes back, re-fetch the current state from the API. Treat real-time as an optimization on top of REST, not a replacement for it.
Echo.connector.pusher.connection.bind('connected', () => {
fetch(`/api/orders/${orderId}`).then(applyOrder);
});
Send compact updates that re-render the source of truth. A OrderStatusChanged event should carry the new status and updated_at. When the client reconnects and gets a fresh fetch, that fetch wins. The real-time event just shortens the delay.
What you should not do: assume every event arrives. The day a customer screenshot shows a stuck "Pending" status while the order is actually delivered, you'll wish you had a fallback fetch.
Reverb Does Not Remove Scaling Questions
Reverb simplifies the stack, but real-time systems still need operational thinking.
WebSocket connections are long-lived. That makes them different from normal HTTP requests. A server handling 10,000 short API requests is not the same as a server holding 10,000 live connections.
Scaling Questions To Ask
- How many concurrent connections do you expect? — Users with open browser tabs count.
- How many events per second will you broadcast? — Chat and dashboards behave differently.
- Do you need Redis or another backend for coordination? — Multi-server setups need shared broadcasting infrastructure.
- How will you monitor connection count? — You need visibility before traffic spikes.
- What happens when clients reconnect? — The UI should recover gracefully.
Real-time architecture is like plumbing. A sink is easy. A building with 200 apartments needs pressure, valves, drains, and maintenance access.
Don't Broadcast Everything
Just because you can broadcast does not mean you should.
Real-time events should be useful, scoped, and reasonably small. Sending giant payloads to every connected client is a great way to create an expensive mess.
A better event payload includes only what the client needs:
public function broadcastWith(): array
{
return [
'order' => [
'id' => $this->order->id,
'status' => $this->order->status,
'updated_at' => $this->order->updated_at?->toISOString(),
],
];
}
This avoids leaking unnecessary fields and keeps the WebSocket payload light.
Good Broadcast Events
- State changes — Order status, job progress, message created.
- Small payloads — IDs, statuses, timestamps, summary fields.
- Scoped channels — User, team, order, or dashboard-specific streams.
- Recoverable UI changes — If the client misses one event, it can reload state.
- Meaningful user value — Real-time should reduce uncertainty.
Real-time should feel like a helpful notification, not a firehose.
Reverb vs External Services
External WebSocket services still have a place.
If you need global edge presence, managed operational support, or you don't want to run WebSocket infrastructure, a provider may be the right call. Reverb is compelling when you want first-party Laravel integration, more control, and fewer external moving parts.
The trade-off is ownership. With Reverb, you control more. That also means you operate more.
It's the difference between owning a kitchen and ordering catering. Both can feed people. One gives you more control; the other gives you less cleanup.
Final Tips
The first time you add real-time updates to a dashboard, users notice immediately. A support team watching live order statuses doesn't care about WebSocket elegance. They care that the screen finally feels alive.
Going forward, start small. Broadcast one valuable event, secure the channel, monitor connections, and only then expand.
Real-time Laravel is practical now. Just don't forget that live data still needs boring engineering discipline. Build it carefully and make it feel instant ⚡



