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=syncwhile testing. - An authenticated user model with the
Notifiabletrait (the defaultUsermodel has it).
Step 1: Install broadcasting and Reverb
php artisan install:broadcastingThe 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:
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=httpThe values above are only examples. The installer generates its own keys for you.
Step 2: Create a notification that broadcasts
php artisan make:notification InvoicePaid<?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:
php artisan make:notifications-table
php artisan migrateStep 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
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
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):
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:
php artisan reverb:start
php artisan queue:work
npm run devHow 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.
// 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
typefield 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.phpmust be identical, including the model namespace dots. - Changing
.envwithout rebuilding the front end. TheVITE_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?
- Open the app in a browser, log in and keep the dev tools console open.
- Run
php artisan tinkerand notify your user:User::first()->notify(new \App\Notifications\InvoicePaid(1, '$10')). - The console should print the message, and the
notificationstable should contain the row. - If nothing arrives, check
php artisan reverb:start --debugoutput 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.



