Backend

Laravel Broadcasting: Real-time Events

Mayur Dabhi
Mayur Dabhi
May 11, 2026
14 min read

Modern web applications are expected to update in real time. Users want to see new messages without refreshing, receive live notifications the moment something happens, and watch dashboards that reflect current data. Polling the server every few seconds is wasteful and slow — what you really need is push. Laravel Broadcasting is the framework's answer to this problem: a clean, driver-based API that lets you broadcast server-side events over persistent WebSocket connections directly to your frontend, with minimal boilerplate and full authentication support.

Under the hood, Broadcasting builds on top of Laravel's event system. When something meaningful happens in your application — a new order is placed, a support ticket is updated, a user sends a message — you fire a regular Laravel event that implements ShouldBroadcast. Laravel serializes that event, dispatches it through a queue worker, and forwards it to a WebSocket server (Pusher, Reverb, or others). The browser receives the payload instantly via a persistent connection established by Laravel Echo. The result is a clean separation between your server-side logic and the real-time delivery layer.

Broadcasting vs Polling

HTTP polling checks for updates every N seconds, wasting bandwidth even when nothing changed. WebSocket broadcasting delivers updates in under 50ms the moment they happen, uses a single persistent connection, and scales to thousands of concurrent users. Use broadcasting whenever you need sub-second latency or per-user event delivery.

How Laravel Broadcasting Works

Before writing any code it helps to understand the data flow. Every broadcast involves five layers working together:

Laravel App fires Event ShouldBroadcast queue Queue Worker processes broadcast job HTTP API WS Server Pusher / Reverb manages channels push Browser Laravel Echo .listen() callback auth request for private/presence channels → Laravel validates → grants access

Laravel Broadcasting data flow — from event fire to browser callback

Choosing a Broadcasting Driver

Laravel supports four broadcasting drivers out of the box. Your choice affects cost, hosting requirements, and latency:

Driver Hosting Best For Cost
Laravel Reverb Self-hosted (PHP) Full control, no third-party dependency Free (server costs only)
Pusher Managed cloud Fast setup, generous free tier Free up to 200k daily messages
Ably Managed cloud Global edge network, high throughput Free up to 6M messages/month
Redis + Socket.io Self-hosted Custom Node.js server setups Free (infrastructure costs)

For new projects in 2026, Laravel Reverb is the recommended choice. It's a first-party WebSocket server written in PHP that runs alongside your Laravel application — no external service accounts, no rate limits, and no additional latency from a third-party edge node. Pusher remains excellent for teams that want a fully managed zero-ops solution.

Installation and Configuration

Installing Laravel Reverb

1

Install Reverb via Artisan

Run the install:broadcasting command. It installs the Reverb server, publishes its config, and wires up the Echo scaffolding automatically.

Terminal
# Install Reverb (first-party WebSocket server)
php artisan install:broadcasting

# This installs reverb, publishes config/reverb.php,
# and sets BROADCAST_CONNECTION=reverb in .env

# Install frontend dependencies
npm install --save-dev laravel-echo
npm install --save pusher-js  # Reverb uses Pusher protocol

# Start Reverb server (separate terminal)
php artisan reverb:start

# Start queue worker (events are queued by default)
php artisan queue:work
2

Configure .env

Set your broadcasting connection and Reverb credentials. The installer pre-fills most of these — verify they are correct.

.env
BROADCAST_CONNECTION=reverb

REVERB_APP_ID=my-app-id
REVERB_APP_KEY=my-app-key
REVERB_APP_SECRET=my-app-secret
REVERB_HOST=localhost
REVERB_PORT=8080
REVERB_SCHEME=http

VITE_REVERB_APP_KEY="${REVERB_APP_KEY}"
VITE_REVERB_HOST="${REVERB_HOST}"
VITE_REVERB_PORT="${REVERB_PORT}"
VITE_REVERB_SCHEME="${REVERB_SCHEME}"
3

Enable the BroadcastServiceProvider

Uncomment App\Providers\BroadcastServiceProvider::class in config/app.php if it isn't already enabled (older Laravel versions).

Queue Driver Required

Broadcastable events are dispatched through Laravel's queue by default. Make sure your QUEUE_CONNECTION is set to database, redis, or another real driver — not sync — otherwise events will block the request cycle and you won't see real-time behavior.

Using Pusher Instead

If you prefer a managed service, swap Reverb for Pusher in three steps:

Terminal + .env (Pusher)
# Install Pusher PHP SDK
composer require pusher/pusher-php-server

# .env settings
BROADCAST_CONNECTION=pusher
PUSHER_APP_ID=your-app-id
PUSHER_APP_KEY=your-app-key
PUSHER_APP_SECRET=your-app-secret
PUSHER_APP_CLUSTER=mt1

VITE_PUSHER_APP_KEY="${PUSHER_APP_KEY}"
VITE_PUSHER_APP_CLUSTER="${PUSHER_APP_CLUSTER}"

Creating Broadcastable Events

A broadcastable event is any Laravel event class that implements the ShouldBroadcast interface. The interface requires a single method, broadcastOn(), that returns the channels the event should be sent to. Use ShouldBroadcastNow if you want to skip the queue and broadcast synchronously — useful for critical alerts, but use sparingly since it blocks the HTTP response.

Terminal
php artisan make:event OrderStatusUpdated
app/Events/OrderStatusUpdated.php
<?php

namespace App\Events;

use App\Models\Order;
use Illuminate\Broadcasting\Channel;
use Illuminate\Broadcasting\InteractsWithSockets;
use Illuminate\Broadcasting\PrivateChannel;
use Illuminate\Contracts\Broadcasting\ShouldBroadcast;
use Illuminate\Foundation\Events\Dispatchable;
use Illuminate\Queue\SerializesModels;

class OrderStatusUpdated implements ShouldBroadcast
{
    use Dispatchable, InteractsWithSockets, SerializesModels;

    public function __construct(public Order $order)
    {
        //
    }

    /**
     * Channels this event should broadcast on.
     * PrivateChannel requires the subscriber to be authorized.
     */
    public function broadcastOn(): array
    {
        return [
            new PrivateChannel('orders.' . $this->order->id),
        ];
    }

    /**
     * Override the event name seen by the frontend.
     * Default would be "App\\Events\\OrderStatusUpdated".
     */
    public function broadcastAs(): string
    {
        return 'order.updated';
    }

    /**
     * Customize the payload sent to the frontend.
     * By default, all public properties are included.
     */
    public function broadcastWith(): array
    {
        return [
            'id'     => $this->order->id,
            'status' => $this->order->status,
            'total'  => $this->order->total,
        ];
    }

    /**
     * Conditional broadcasting — only fire when truly changed.
     */
    public function broadcastWhen(): bool
    {
        return $this->order->wasChanged('status');
    }
}

Firing the event from a controller or service is identical to any other Laravel event:

app/Http/Controllers/OrderController.php
public function updateStatus(Request $request, Order $order): JsonResponse
{
    $order->update(['status' => $request->status]);

    // This queues the broadcast — non-blocking
    OrderStatusUpdated::dispatch($order);

    return response()->json(['message' => 'Status updated']);
}

Channel Types and Authorization

Laravel Broadcasting supports three channel types, each with different access semantics. Choosing the right type is important both for security and for the features available on the frontend.

Public Channels

Any client can subscribe to a public channel without authentication. Use them for truly public data: live sports scores, stock tickers, public chat rooms.

Public Channel Event
public function broadcastOn(): array
{
    return [
        new Channel('announcements'),
    ];
}

Private Channels

Private channels require the subscriber to pass an authorization check on your server before the WebSocket server allows the connection. This is where you enforce business rules: "only the order owner can listen to order updates."

Authorization callbacks live in routes/channels.php. Return true to grant access, false to deny:

routes/channels.php
<?php

use App\Models\Order;
use Illuminate\Support\Facades\Broadcast;

// Private channel: user can only subscribe to their own orders
Broadcast::channel('orders.{orderId}', function ($user, $orderId) {
    $order = Order::find($orderId);
    return $order && $order->user_id === $user->id;
});

// Private channel: only admins can subscribe
Broadcast::channel('admin.dashboard', function ($user) {
    return $user->hasRole('admin');
});

// Using a Gate policy for cleaner authorization
Broadcast::channel('orders.{order}', function ($user, Order $order) {
    return $user->can('view', $order);
});

Presence Channels

Presence channels extend private channels with membership awareness. When a user subscribes, all other subscribers are notified. You can see who is currently online in a room — perfect for collaborative tools, live dashboards, and "X is typing" indicators.

Presence Channel Authorization
// Return user data (not just true/false) for presence channels
Broadcast::channel('chat.{roomId}', function ($user, $roomId) {
    if ($user->canJoinRoom($roomId)) {
        // Returned array is shared with all members in the channel
        return [
            'id'     => $user->id,
            'name'   => $user->name,
            'avatar' => $user->avatar_url,
        ];
    }
});

// Presence event
class UserJoinedRoom implements ShouldBroadcast
{
    public function broadcastOn(): array
    {
        return [
            new PresenceChannel('chat.' . $this->roomId),
        ];
    }
}

Frontend Integration with Laravel Echo

Laravel Echo is a JavaScript library that wraps the WebSocket driver (Pusher JS) and provides an expressive API for subscribing to channels and listening to events. Configure it once in your resources/js/bootstrap.js or resources/js/echo.js:

resources/js/bootstrap.js (Reverb)
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'],
});

Listening to Events

JavaScript — Subscribing to Channels
// Public channel — no auth required
Echo.channel('announcements')
    .listen('AnnouncementPublished', (e) => {
        console.log('New announcement:', e.title);
        showToast(e.title, e.message);
    });

// Private channel — server authorizes via routes/channels.php
// broadcastAs() name used here with leading dot
Echo.private(`orders.${orderId}`)
    .listen('.order.updated', (e) => {
        document.getElementById('order-status').textContent = e.status;
        updateProgressBar(e.status);
    });

// Presence channel — membership events included
Echo.join(`chat.${roomId}`)
    .here((members) => {
        // Called immediately with current member list
        updateOnlineList(members);
    })
    .joining((member) => {
        // Called when a new user joins
        addToOnlineList(member);
        showSystemMessage(`${member.name} joined`);
    })
    .leaving((member) => {
        // Called when a user disconnects
        removeFromOnlineList(member.id);
    })
    .listen('MessageSent', (e) => {
        appendMessage(e.user, e.message);
    })
    .whisper('typing', { name: currentUser.name });  // client-only events

Real-Time Notifications

Laravel's notification system integrates directly with Broadcasting. Implement toBroadcast() in your notification class and Echo can listen on the user's personal notification channel with zero extra setup:

app/Notifications/InvoicePaid.php
<?php

namespace App\Notifications;

use Illuminate\Bus\Queueable;
use Illuminate\Notifications\Messages\BroadcastMessage;
use Illuminate\Notifications\Notification;

class InvoicePaid extends Notification
{
    use Queueable;

    public function __construct(private readonly float $amount) {}

    public function via($notifiable): array
    {
        // Send both email and real-time broadcast
        return ['mail', 'broadcast'];
    }

    public function toBroadcast($notifiable): BroadcastMessage
    {
        return new BroadcastMessage([
            'amount'  => $this->amount,
            'message' => "Your invoice of \${$this->amount} has been paid.",
        ]);
    }
}
JavaScript — Listening to User Notifications
// Laravel maps this to the authenticated user's private channel automatically
Echo.private(`App.Models.User.${userId}`)
    .notification((notification) => {
        if (notification.type === 'App\\Notifications\\InvoicePaid') {
            showNotificationBadge(notification.message);
        }
    });

Production Deployment

When deploying Reverb to production, run it as a supervised process and put Nginx in front of it to handle TLS termination:

Supervisor config (/etc/supervisor/conf.d/reverb.conf)
[program:reverb]
command=php /var/www/myapp/artisan reverb:start --host=0.0.0.0 --port=8080
autostart=true
autorestart=true
user=www-data
redirect_stderr=true
stdout_logfile=/var/log/supervisor/reverb.log
Nginx — WebSocket proxy block
server {
    listen 443 ssl;
    server_name myapp.com;

    # Proxy WebSocket connections to Reverb
    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_read_timeout     60s;
    }
}

Key Takeaways

  • Driver-based: Swap between Reverb, Pusher, and Ably by changing one .env value — your event code stays identical
  • Queue your broadcasts: Use ShouldBroadcast (queued) over ShouldBroadcastNow in production to avoid blocking HTTP responses
  • Authorize private channels in routes/channels.php — never skip this step for user-specific data
  • Use broadcastAs() to give events short, stable names that won't break if you rename the PHP class
  • Presence channels give you real-time membership lists with almost no extra code — great for collaborative features
  • Notifications integrate natively — add 'broadcast' to via() and implement toBroadcast() for instant push notifications

Laravel Broadcasting elegantly bridges your server-side events and your users' browsers. Once the infrastructure is in place — a running Reverb server and a queue worker — adding real-time behavior to any feature is as simple as implementing ShouldBroadcast and calling Echo.private(...).listen(...). The days of polling are behind you.

Laravel Broadcasting Real-time WebSockets Laravel Reverb Pusher Laravel Echo
Mayur Dabhi

Mayur Dabhi

Full Stack Developer with 5+ years of experience building scalable web applications with Laravel, React, and Node.js.