Kokil Thapa - Professional Web Developer in Nepal
Freelancer Web Developer in Nepal with 15+ Years of Experience

Kokil Thapa is an experienced full-stack web developer focused on building fast, secure, and scalable web applications. He helps businesses and individuals create SEO-friendly, user-focused digital platforms designed for long-term growth.

Building Realtime APIs with WebSockets and Laravel Reverb

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.

Realtime API: HTTP + WebSocketBrowserEcho clientLaravel 13REST + eventsReverbWebSocketHTTPpushWebSocket subscribeMySQL 9.7source of truthRedis 8.10queue + cacheSanctumchannel auth
Building Realtime APIs with WebSockets and Laravel Reverb: HTTP writes data, Reverb pushes events, Redis backs queues.

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

  1. Install the package: composer require laravel/reverb
  2. Run the installer: php artisan reverb:install
  3. Set BROADCAST_CONNECTION=reverb in .env
  4. Start the server locally: php artisan reverb:start
  5. 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.

Broadcast Event PipelineAPI POSTControllervalidatesEventRedisQueue Workerbroadcast jobReverbWebSocketConnected Clients Receive status.changedEcho listens on private-bookings.{id}
Queued broadcast pipeline: API write, event dispatch, worker push via Reverb, Echo update in the browser.

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.

OptionHostingCostBest forTrade-off
Laravel ReverbSelf-hosted on your VPSFree (server cost only)Laravel 13 apps, full control, Nepal VPS budgetsYou manage process supervision and TLS
PusherManaged SaaSUsage-based (~USD 49+/mo at scale)Fast launch, no DevOps staffPer-connection pricing adds up
SoketiSelf-hosted Node serviceFree (server cost only)Teams already running Node infraExtra 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.

Pick Your Realtime BackendNeed realtime?Own VPS?Ubuntu + PHPNo DevOps?Pay SaaSLaravel ReverbrecommendedPusher SaaSmanagedSoketi: legacy Node choice
Decision tree: self-hosted Laravel Reverb for PHP teams, Pusher when ops budget is traded for convenience.

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.

Production Reverb TopologyClientsNginxTLS + proxyPHP-FPMReverbRedisAPIWSCommon Production GotchasQueue worker not running after deployVITE_ env mismatch breaks EchoMissing Upgrade headers on proxy
Production stack: Nginx terminates TLS, proxies WebSockets to Reverb, Laravel serves REST via PHP-FPM.

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.log and 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, set BROADCAST_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

Laravel Reverb is Laravel’s first-party WebSocket server. It speaks the Pusher protocol, so Laravel Echo and most Pusher client libraries work unchanged. Your REST API still handles writes and validation; after the database commit, broadcastable events push notifications to subscribed clients in milliseconds while HTTP stays cacheable for one-off reads.

Laravel 13 with PHP 8.3 or higher. PHP 8.5 runs fine on Ubuntu 24 servers. Use Composer 2.10 for installs.

Run composer require laravel/reverb, then php artisan reverb:install. Set BROADCAST_CONNECTION=reverb in .env with REVERB_APP_ID, REVERB_APP_KEY, REVERB_APP_SECRET, REVERB_HOST, REVERB_PORT, and REVERB_SCHEME. Mirror those values in Vite with VITE_ prefixes. Start locally with php artisan reverb:start and run php artisan queue:work because broadcasts often queue. Confirm routes/channels.php exists for private channel rules.

Generate an event class, implement ShouldBroadcast, define broadcastOn channels, broadcastAs name, and broadcastWith payload. Dispatch after your controller persists the model—the HTTP response still returns JSON while other clients receive the push. Use ShouldBroadcastNow only when latency matters more than throughput; queued broadcasts scale better under load. On the client, Echo.private listens with a leading dot before the event name matching broadcastAs.

Yes. Think of two lanes: HTTP handles commands—create booking, upload file, pay invoice—while WebSockets handle notifications like booking confirmed or payment captured. Clients needing data once keep using REST; clients needing live UI updates open a WebSocket alongside normal API calls. Reverb adds a broadcast layer without replacing HTTP routes.

Install laravel-echo and pusher-js with npm 12. In your JavaScript bootstrap, set broadcaster to reverb and pass VITE_REVERB_APP_KEY, VITE_REVERB_HOST, VITE_REVERB_PORT, and VITE_REVERB_SCHEME from your Vite env. Set authEndpoint to /broadcasting/auth. For Sanctum SPAs, pass Authorization Bearer with your API token in auth headers; cookie-based sessions work when Echo sends the CSRF cookie automatically.

Public channels need no auth; private and presence channels call /broadcasting/auth before Reverb accepts the subscription. Define authorization in routes/channels.php—return false or user data based on ownership or permissions. Sanctum and Passport both work when middleware authenticates the auth request. Rate-limit /broadcasting/auth separately because brute-force channel probes can hammer it. Never put sensitive data on public channels; a leaked channel name alone lets someone listen.

All speak the same client protocol when configured correctly. Reverb is self-hosted, free aside from server cost, ships with Laravel 13, and suits Nepal VPS budgets—you manage process supervision and TLS. Pusher is managed SaaS at roughly USD 49+/month at scale (Rs 6,000–15,000/month, ~USD 45–110), ideal when you want zero WebSocket ops. Soketi is self-hosted Node, free aside from server cost, fine if you standardized on it before Reverb matured. For most Laravel shops, Reverb wins on simplicity.

Reverb itself is free—you only pay for your VPS or server hosting. Managed Pusher alternative runs roughly Rs 6,000–15,000/month (~USD 45–110) depending on connection volume.

The most common cause is a missing queue worker after deploy. Broadcasts often queue through Redis; without php artisan queue:work running under Supervisor, events never reach Reverb. Also verify Reverb itself is supervised, Nginx proxies WebSocket Upgrade headers correctly, production .env sets REVERB_SCHEME=https and REVERB_HOST to your public domain, and Vite env vars match. I've debugged this repeatedly after Deployer symlink swaps when workers were not restarted.

Run Reverb under Supervisor with php artisan reverb:start --host=127.0.0.1 --port=8080, logging to storage/logs/reverb.log. Add a separate Supervisor program for queue:work. Configure Nginx to proxy /app to 127.0.0.1:8080 with proxy_http_version 1.1, Upgrade and Connection headers, and a 60-second read timeout. Terminate TLS at Nginx, set REVERB_SCHEME=https, reload PHP-FPM after deploy for Opcache, and use a dedicated port per app on shared EC2 hosts.

ShouldBroadcast queues the broadcast through Redis, which scales better under load because the HTTP request returns before Reverb delivers the event. ShouldBroadcastNow sends immediately, useful when latency matters more than request throughput. Production apps with moderate traffic should default to queued broadcasts and ensure a worker is always running.

Set QUEUE_CONNECTION=redis and REDIS_CLIENT=phpredis in .env. Queue workers process broadcast jobs; without Redis and a running worker, events sit in the queue and never reach clients. Redis 8.10 also commonly backs session storage on the same instance. When connection counts grow, review scaling patterns for WebSockets and Redis together.

Booking and inventory live counts—trek departures, hotel rooms, appointment slots. Order and payment status after eSewa or Khalti callbacks flip UI from pending to paid without polling. Client portals for document upload progress and lawyer review status. Admin dashboards pushing queue depth, failed jobs, or new lead counts. Chat and support threads where message order matters. Skip WebSockets for rarely visited report pages where a thirty-second poll or manual refresh is cheaper.

Open browser devtools, Network tab, WS filter, and confirm a 101 Switching Protocols response. Dispatch a test event via Tinker: event(new App\Events\BookingStatusChanged($booking)). Watch storage/logs/reverb.log and queue worker output. Load-test with two browsers on the same private channel to verify auth isolation—only authorized users should receive events. Document your event catalog alongside REST endpoints so frontend developers know both pull and push contracts.

Share this article

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.

Quick Contact Options
Choose how you want to connect me: