
September 08, 2026
13 min read
By Kokil Thapa | Last reviewed: September 2026
Your REST API can answer requests, but it cannot tell the browser something changed unless the client polls again. Building Realtime APIs with WebSockets and Laravel Reverb closes that gap. You keep Laravel 13 as the source of truth while Reverb pushes events to connected clients over a persistent WebSocket. That pattern fits booking dashboards, order status screens, chat threads, and legal document upload notifications. If you already ship RESTful APIs with Laravel, Reverb adds a broadcast layer without replacing your HTTP routes.
What is Laravel Reverb and how does it fit a realtime API?
Reverb is Laravel’s first-party WebSocket server. It speaks the Pusher protocol, so Laravel Echo and most Pusher client libraries work unchanged. Your API receives a POST, validates it, writes to MySQL or PostgreSQL, then fires a broadcastable event. Reverb delivers that event to every subscribed socket in milliseconds.
Think of two lanes on the same app. HTTP handles commands: create booking, upload file, pay invoice. WebSockets handle notifications: “booking confirmed”, “file scanned”, “payment captured”. Clients that only need data once can keep using REST. Clients that need live UI updates open a WebSocket alongside normal API calls.
On a trek booking platform I built with Laravel and Livewire, staff needed live seat counts without refreshing. The API still accepted bookings over POST. A BookingUpdated event broadcast the new availability to every open admin tab. That split keeps your API cacheable and your UI reactive. For deeper background, see the guide on real-time Laravel with Reverb and WebSockets.
Official docs live at Laravel Reverb documentation and the Laravel broadcasting guide. Read both before you touch production Nginx config.
How do you install and configure Laravel Reverb in a Laravel 13 project?
Start with PHP 8.3 or higher. Laravel 13 requires it. PHP 8.5 runs fine on Ubuntu 24 servers I maintain. Use Composer 2.10 for dependency installs.
Install Reverb and enable broadcasting
- Install the package:
composer require laravel/reverb - Run the installer:
php artisan reverb:install - Set
BROADCAST_CONNECTION=reverbin.env - Start the server locally:
php artisan reverb:start - Run a queue worker because broadcasts often queue:
php artisan queue:work
The installer publishes config/reverb.php and adds Reverb keys to your environment file. Match those keys on the server and in your Vite frontend bundle.
# .env (backend)
BROADCAST_CONNECTION=reverb
REVERB_APP_ID=my-app
REVERB_APP_KEY=my-key
REVERB_APP_SECRET=my-secret
REVERB_HOST=127.0.0.1
REVERB_PORT=8080
REVERB_SCHEME=http
QUEUE_CONNECTION=redis
REDIS_CLIENT=phpredis # .env (Vite — prefix with VITE_)
VITE_REVERB_APP_KEY="${REVERB_APP_KEY}"
VITE_REVERB_HOST="${REVERB_HOST}"
VITE_REVERB_PORT="${REVERB_PORT}"
VITE_REVERB_SCHEME="${REVERB_SCHEME}" Enable the broadcast service provider if it is still commented out in older apps. Laravel 13 ships with broadcasting ready. Confirm routes/channels.php exists. That file defines who may join each private channel.
Frontend: Laravel Echo with the Reverb connector
Install client libraries with npm 12:
npm install --save-dev laravel-echo pusher-js Configure Echo in your JavaScript bootstrap file:
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,
wssPort: import.meta.env.VITE_REVERB_PORT,
forceTLS: import.meta.env.VITE_REVERB_SCHEME === 'https',
enabledTransports: ['ws', 'wss'],
authEndpoint: '/broadcasting/auth',
auth: {
headers: {
Authorization: `Bearer ${localStorage.getItem('api_token')}`,
},
},
}); If your SPA uses Sanctum tokens, pass the Bearer header exactly as shown. Cookie-based sessions work too when Echo sends the CSRF cookie automatically. Our Sanctum API authentication guide covers token issuance patterns that pair well with private channels.
How do you broadcast events from a Laravel API to WebSocket clients?
Every realtime feature starts with an event class. Generate one with Artisan, implement ShouldBroadcast, and return the channels plus payload shape you want clients to receive.
Create a broadcastable event
php artisan make:event BookingStatusChanged <?php
namespace App\Events;
use App\Models\Booking;
use Illuminate\Broadcasting\PrivateChannel;
use Illuminate\Contracts\Broadcasting\ShouldBroadcast;
use Illuminate\Foundation\Events\Dispatchable;
use Illuminate\Queue\SerializesModels;
class BookingStatusChanged implements ShouldBroadcast
{
use Dispatchable, SerializesModels;
public function __construct(public Booking $booking) {}
public function broadcastOn(): array
{
return [
new PrivateChannel('bookings.' . $this->booking->id),
];
}
public function broadcastAs(): string
{
return 'status.changed';
}
public function broadcastWith(): array
{
return [
'id' => $this->booking->id,
'status' => $this->booking->status,
'updated_at' => $this->booking->updated_at->toIso8601String(),
];
}
} Dispatch the event after your controller persists the model:
public function update(UpdateBookingRequest $request, Booking $booking)
{
$booking->update($request->validated());
BookingStatusChanged::dispatch($booking);
return new BookingResource($booking);
} The HTTP response still returns JSON for the caller. Every other subscribed client gets the WebSocket push. Use ShouldBroadcastNow only when latency matters more than request throughput. Queued broadcasts scale better under load.
Subscribe on the client side:
Echo.private(`bookings.${bookingId}`)
.listen('.status.changed', (payload) => {
console.log('New status:', payload.status);
}); Note the leading dot before status.changed. That matches broadcastAs(). Payload keys should stay small. Send IDs and changed fields, not full model graphs. Validate JSON shape during development with the site JSON formatter tool.
Follow general patterns from Laravel API best practices for versioning and error format. Realtime payloads should mirror the same field naming your REST resources expose.
How do you secure private and presence channels with Laravel Reverb?
Public channels need no auth. Private and presence channels call your /broadcasting/auth endpoint before Reverb accepts the subscription. Never put sensitive data on public channels. A leaked channel name is enough for someone to listen.
Define channel authorization rules
<?php
use App\Models\Booking;
use Illuminate\Support\Facades\Broadcast;
Broadcast::channel('bookings.{bookingId}', function ($user, int $bookingId) {
$booking = Booking::find($bookingId);
if (! $booking) {
return false;
}
return $user->id === $booking->user_id
|| $user->can('view-bookings');
}); Sanctum guards work here when your API middleware stack authenticates the auth request. Passport works too for OAuth clients. Compare approaches in our write-up on Passport vs Sanctum for API auth.
Rate-limit the auth endpoint separately. A brute-force channel probe can hammer /broadcasting/auth. Apply throttle middleware the same way you would on login routes. See rate limiting and API throttling in Laravel for Redis-backed limiters.
Presence channels for collaborative UIs
Presence channels extend private channels with member lists. They suit document co-editing, support inbox assignment, or live CRM views.
Broadcast::channel('office.{teamId}', function ($user, int $teamId) {
if (! $user->teams->contains('id', $teamId)) {
return false;
}
return ['id' => $user->id, 'name' => $user->name];
}); On a legal-tech client portal, I used private channels per case file so only the lawyer and assigned client saw upload status events. The REST API still enforced policy checks on every download URL. WebSocket auth duplicated that boundary at subscription time, not at every message.
Laravel Reverb vs Pusher vs Soketi — which realtime backend should you choose?
You have three common backends for Laravel broadcasting. All speak the same client protocol when configured correctly.
| Option | Hosting | Cost | Best for | Trade-off |
|---|---|---|---|---|
| Laravel Reverb | Self-hosted on your VPS | Free (server cost only) | Laravel 13 apps, full control, Nepal VPS budgets | You manage process supervision and TLS |
| Pusher | Managed SaaS | Usage-based (~USD 49+/mo at scale) | Fast launch, no DevOps staff | Per-connection pricing adds up |
| Soketi | Self-hosted Node service | Free (server cost only) | Teams already running Node infra | Extra stack beside PHP |
For most Laravel shops in Nepal, Reverb wins on simplicity. It ships with the framework, uses PHP, and matches the docs your team already reads. Pusher makes sense when you want zero WebSocket ops and can absorb Rs 6,000–15,000/month (~USD 45–110) in service fees. Soketi is a fine fallback if you standardized on it before Reverb matured.
Redis still matters with Reverb. Queue workers and session storage often share the same Redis 8.10 instance. Read building real-time features with WebSockets and Redis for scaling patterns when connection counts grow.
How do you deploy Laravel Reverb behind Nginx on a production server?
Local reverb:start is fine for development. Production needs a supervised process, TLS termination, and a reverse proxy that upgrades HTTP to WebSocket.
Supervisor keeps Reverb alive
[program:reverb]
process_name=%(program_name)s
command=php /var/www/myapp/current/artisan reverb:start --host=127.0.0.1 --port=8080
autostart=true
autorestart=true
user=www-data
redirect_stderr=true
stdout_logfile=/var/www/myapp/shared/storage/logs/reverb.log Run a separate Supervisor program for queue:work. Without a worker, queued broadcasts never leave Redis. I have debugged “WebSockets work on my laptop but not production” more than once. The queue worker was simply not running after a Deployer symlink swap.
Nginx WebSocket proxy block
location /app {
proxy_pass http://127.0.0.1:8080;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "Upgrade";
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_read_timeout 60s;
} Set REVERB_HOST to your public domain and REVERB_SCHEME=https in production. Vite env vars must match. After deploy, reload PHP-FPM so Opcache picks up config changes. Our Linux system administration service covers the same Supervisor plus Nginx patterns on Ubuntu 22/24.
On sister sites that share a Deployer 7 pipeline, I run Reverb on a dedicated port per app. Each site gets its own Supervisor stanza. Shared EC2 is viable for moderate connection counts. Split Reverb onto its own small VPS when you exceed a few thousand concurrent sockets.
Channel auth signatures follow the Pusher spec. The Pusher auth signature documentation explains the HMAC format if you debug custom clients.
Testing before go-live
- Open browser devtools → Network → WS tab and confirm a 101 Switching Protocols response.
- Dispatch a test event with Tinker:
event(new App\Events\BookingStatusChanged($booking)); - Watch
storage/logs/reverb.logand your queue worker output. - Load-test with two browsers on the same private channel to verify auth isolation.
Document your event catalog the same way you document REST endpoints. Pair this work with API documentation using Scribe so frontend devs know both pull and push contracts.
What are practical use cases for realtime Laravel APIs?
Not every screen needs WebSockets. Reach for Reverb when stale data creates support tickets or lost revenue.
- Booking and inventory: Trek departures, hotel rooms, appointment slots—show remaining capacity live. Our Adventure Third Pole Trek booking platform is a natural fit for this pattern.
- Order and payment status: eSewa or Khalti callbacks update the DB; a broadcast flips the UI from “pending” to “paid” without polling.
- Client portals: Document upload progress and lawyer review status on portals like those built for Mijar Law Associates.
- Admin dashboards: Queue depth, failed jobs, or new lead counts pushed to operations staff.
- Chat and notifications: Support threads where message order matters.
Skip WebSockets for rarely visited report pages. A thirty-second poll or a manual refresh button is cheaper to build and operate. Match transport to user expectation and traffic shape.
Frontend integration paths vary. Blade plus Alpine suits simple toast notifications. A Vue SPA pairs cleanly—see Vue with Laravel setup. Livewire can listen via JavaScript hooks; the Livewire beginner tutorial covers component patterns that accept external events.
If you need help scoping a realtime layer on an existing API, review our API development service or the broader web development offering. For long-term monitoring after launch, support and maintenance keeps workers and Reverb processes healthy.
Key Takeaways
- Keep REST for commands; use Reverb WebSockets for push notifications after the database commit succeeds.
- Install Reverb with
artisan reverb:install, setBROADCAST_CONNECTION=reverb, and run both Reverb and a queue worker in production. - Authorize every private channel in
routes/channels.php; never expose sensitive payloads on public channels. - Terminate TLS at Nginx, proxy with WebSocket Upgrade headers, and supervise Reverb with Supervisor.
- Prefer self-hosted Reverb on Laravel 13 when you already manage Ubuntu VPS infrastructure; use Pusher only when managed ops outweighs cost.
- Broadcast small payload diffs, document events alongside REST endpoints, and test with two authenticated clients before release.
People Also Ask
Does Laravel Reverb replace my REST API?
No. Reverb delivers events to connected clients. Your API still handles create, read, update, and delete over HTTP. Clients typically call REST first, then listen on a WebSocket for changes that affect other users or tabs.
Do I need Redis to run Laravel Reverb?
Reverb itself does not require Redis, but queued broadcasting does. Most production apps set QUEUE_CONNECTION=redis so broadcast jobs do not block HTTP responses. Redis 8.10 also backs cache and session storage on the same server.
Can Laravel Reverb scale horizontally?
Yes, with a shared Redis pub/sub backend across multiple Reverb instances. A single VPS handles moderate traffic. Add instances behind a load balancer when concurrent connections grow into the thousands.
How is Reverb different from Laravel Echo?
Reverb is the server. Echo is the JavaScript client that subscribes to channels. You run Reverb on your infrastructure; you import Echo in your frontend bundle built with Vite 8.x.
Ship your realtime Laravel API with confidence
Building Realtime APIs with WebSockets and Laravel Reverb is the default stack I recommend for Laravel 13 projects that outgrow polling. You keep the API you already test with Postman. You add a thin broadcast layer that makes dashboards and portals feel instant. Start with one private channel, one event, and one screen. Prove auth and deploy config there before you broadcast every model change.
Need hands-on help wiring Reverb into an existing app or booking workflow? Contact us to talk through architecture, or read Laravel broadcasting with Reverb complete setup and how to build a REST API in Laravel the right way for the HTTP foundation your realtime layer should sit on.
Frequently Asked Questions
0 Comments
Leave a comment
Your email is not published. Comments appear once they have been read. Sign in to have your details filled in.

