Muhammad Yasir Amin

Loading websiteโ€ฆ

More from Muhammad Yasir Amin

Build Along With Me

Practical walkthroughs on Laravel, React, APIs and deployment workflows.

Subscribe on YouTube

Laravel 13 Reverb Real-Time Notifications: Step-by-Step Tutorial

๐Ÿ› ๏ธ Laravel Tutorials5 min read

Laravel 13 Reverb Real-Time Notifications: Step-by-Step Tutorial

Muhammad Yasir Amin

Muhammad Yasir Amin

Senior Full Stack Engineerโ€ขNov 7, 2026
Laravel 13 Reverb Real-Time Notifications: Step-by-Step Tutorial

To send real-time notifications in Laravel 13, install broadcasting with Reverb using php artisan install:broadcasting, make your notification implement a broadcast channel, and listen for it on a private channel with Laravel Echo. This guide targets Laravel 13 and PHP 8.3 or newer.

What is Laravel Reverb?

Reverb is the first-party WebSocket server for Laravel. It speaks the Pusher protocol, so Laravel Echo and the Pusher JavaScript client can connect to it. You run it yourself, without a paid third-party service.

Prerequisites

  • Laravel 13, PHP 8.3 or newer, Node.js and npm for the front-end build.
  • A queue connection. Broadcast notifications are queued, so run a worker or set QUEUE_CONNECTION=sync while testing.
  • An authenticated user model with the Notifiable trait (the default User model has it).

Step 1: Install broadcasting and Reverb

Code
php artisan install:broadcasting

The command asks whether you want Reverb, creates routes/channels.php and config/broadcasting.php, adds REVERB_* keys to .env and installs the Echo packages. After it finishes, check that .env contains:

SQL
BROADCAST_CONNECTION=reverb
REVERB_APP_ID=local-app
REVERB_APP_KEY=local-key
REVERB_APP_SECRET=local-secret
REVERB_HOST=localhost
REVERB_PORT=8080
REVERB_SCHEME=http

The values above are only examples. The installer generates its own keys for you.

Step 2: Create a notification that broadcasts

Code
php artisan make:notification InvoicePaid
JavaScript
<?php

namespace App\Notifications;

use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Notifications\Messages\BroadcastMessage;
use Illuminate\Notifications\Notification;

class InvoicePaid extends Notification implements ShouldQueue
{
    use Queueable;

    public function __construct(public int $invoiceId, public string $amount)
    {
    }

    public function via(object $notifiable): array
    {
        return ['database', 'broadcast'];
    }

    public function toArray(object $notifiable): array
    {
        return [
            'invoice_id' => $this->invoiceId,
            'message' => "Invoice #{$this->invoiceId} was paid ({$this->amount}).",
        ];
    }

    public function toBroadcast(object $notifiable): BroadcastMessage
    {
        return new BroadcastMessage($this->toArray($notifiable));
    }
}

The database channel stores a copy so users see missed notifications after a reload. For that you need the table:

Code
php artisan make:notifications-table
php artisan migrate

Step 3: Authorise the private channel

User notifications are broadcast on a private channel named after the user model. Laravel already defines the authorisation callback in a fresh app, but confirm it in routes/channels.php:

PHP
<?php

use Illuminate\Support\Facades\Broadcast;

Broadcast::channel('App.Models.User.{id}', function ($user, $id) {
    return (int) $user->id === (int) $id;
});

Step 4: Send the notification

PHP
use App\Notifications\InvoicePaid;

$user->notify(new InvoicePaid($invoice->id, '$49.00'));

Step 5: Listen with Laravel Echo

Add this to your JavaScript after Echo is configured (the installer creates resources/js/echo.js):

JavaScript
const userId = document.querySelector('meta[name="user-id"]').content;

window.Echo.private('App.Models.User.' + userId)
    .notification((notification) => {
        console.log('New notification:', notification.message);
    });

Expose the id with <meta name="user-id" content="{{ auth()->id() }}"> in your layout. Then start the processes, each in its own terminal:

Code
php artisan reverb:start
php artisan queue:work
npm run dev

How do you show unread notifications after a page reload?

Real-time delivery only reaches users who are online. For everyone else, read the stored rows when the page loads. The Notifiable trait gives you relationships for that.

Shell
// routes/web.php
Route::get('/notifications', function (Request $request) {
    return $request->user()
        ->unreadNotifications()
        ->latest()
        ->take(10)
        ->get(['id', 'data', 'created_at']);
})->middleware('auth');

Route::post('/notifications/read', function (Request $request) {
    $request->user()->unreadNotifications->markAsRead();

    return response()->noContent();
})->middleware('auth');

On the front end, call /notifications once at page load to fill the list, then prepend each new item received through Echo. Call the read route when the user opens the dropdown, so the unread counter resets.

Which broadcast details can you change?

  • Channel name: define receivesBroadcastNotificationsOn() on the notifiable model if you want a channel name other than the default.
  • Payload: toBroadcast() controls the data that reaches the browser. Send only what the interface needs, never whole models with private attributes.
  • Queue: set public $queue = 'broadcasts' on the notification and run a worker for that queue if you want broadcast traffic separate from heavy jobs.
  • Type: broadcast messages include a type field with the notification class name, which you can use in the browser to pick an icon.

What should you know about security?

Private channels call /broadcasting/auth to check the callback in routes/channels.php. If that request returns 403 or 419, the browser never subscribes. Keep your session cookie and CSRF configuration working for the domain that serves the page, especially if your front end and Reverb run on different hosts.

Common mistakes

  • Forgetting the queue worker, so the notification is stored but never broadcast.
  • Using a public channel for user data. Notifications must go through a private channel.
  • Mismatched channel names. The Echo name and the name in routes/channels.php must be identical, including the model namespace dots.
  • Changing .env without rebuilding the front end. The VITE_REVERB_* values are read at build time.
  • Leaving the default local keys in production. Generate new ones and put Reverb behind TLS.

How do you verify it works?

  1. Open the app in a browser, log in and keep the dev tools console open.
  2. Run php artisan tinker and notify your user: User::first()->notify(new \App\Notifications\InvoicePaid(1, '$10')).
  3. The console should print the message, and the notifications table should contain the row.
  4. If nothing arrives, check php artisan reverb:start --debug output for connection and subscription events.

FAQ

Do I need Reverb for notifications?

No. Reverb is one of the supported broadcasters. You can use Pusher, Ably or Laravel Cloud WebSockets with the same Laravel code.

Why are notifications delayed?

Broadcast notifications implement queues. A slow or stopped worker delays them, so check the queue first.

Can I notify without storing it in the database?

Yes. Remove database from the via() array. Users then miss notifications sent while they were offline.

How do I run Reverb in production?

Run reverb:start under a process manager such as Supervisor, proxy it through your web server with TLS, and review the scaling section of the Reverb documentation.

References

Article Index & Topic Tags
LaravelReverbNotificationsBroadcasting

YouTube Channel

Build Along With Me

Practical walkthroughs on Laravel, React, APIs and deployment workflows.

Muhammad Yasir Amin
Available for Contracts
Author & Solutions Architect

Senior Full Stack Engineer

Independent Senior Full Stack Engineer working on Laravel, React and Next.js applications, APIs and delivery workflows.

Laravel & PHP ApplicationsReact & Next.js InterfacesREST APIs & IntegrationsDatabase Queries & DebuggingDocker Development WorkflowsClear Scope & Handover
Discuss Your Next Step

What Does Your Application Need Next?

Share your current system, the problem you want to solve and what a useful result looks like. We can identify the right starting point and agree on a practical scope.

Technical Audit

Find the Next Useful Fix

Application Development

Scoped, Reviewable Milestones

Ongoing Support

Agreed Capacity and Priorities

Share your goals, current stack and requirements before the call.