Laravel Reverb Presence Channels &amp; Typed Events | Mohamed Said        [  ![Mohamed Said](https://cdn.msaied.com/01KT78WE565VEMM3PSNQAAB0MH.png)   Mohamed Said Laravel Backend Engineer  ](https://msaied.com) [ Home ](https://msaied.com) [ Projects ](https://msaied.com/projects) [ Articles  ](https://msaied.com/articles) [ Certificates ](https://msaied.com/certificates) [ Contact ](https://msaied.com#contact-section) 

       [  ](https://github.com/EG-Mohamed)       

 [ Home ](https://msaied.com) [ Projects ](https://msaied.com/projects) [ Articles ](https://msaied.com/articles) [ Certificates ](https://msaied.com/certificates) [ Contact ](https://msaied.com#contact-section) 

  [ home ](https://msaied.com)    [ articles ](https://msaied.com/articles)    Laravel Reverb: Building Presence Channels with Per-User State and Typed Events        On this page       1. [  Presence Channels in Laravel Reverb: Per-User State and Typed Events ](#presence-channels-in-laravel-reverb-per-user-state-and-typed-events)
2. [  Authenticating and Enriching Channel Membership ](#authenticating-and-enriching-channel-membership)
3. [  Typed Broadcast Events ](#typed-broadcast-events)
4. [  Handling Reconnects Without Ghost Members ](#handling-reconnects-without-ghost-members)
5. [  Scaling Across Multiple Reverb Workers ](#scaling-across-multiple-reverb-workers)
6. [  Key Takeaways ](#key-takeaways)

  ![Laravel Reverb: Building Presence Channels with Per-User State and Typed Events](https://cdn.msaied.com/352/9b3c490b8303fdc84442671965a3ee8a.png)

  #laravel   #reverb   #websockets   #broadcasting  

 Laravel Reverb: Building Presence Channels with Per-User State and Typed Events 
=================================================================================

     3 Jul 2026      3 min read    ![Mohamed Said](https://cdn.msaied.com/01KT78WE565VEMM3PSNQAAB0MJ.jpg)  Mohamed Said  

       Table of contents

1. [  01   Presence Channels in Laravel Reverb: Per-User State and Typed Events  ](#presence-channels-in-laravel-reverb-per-user-state-and-typed-events)
2. [  02   Authenticating and Enriching Channel Membership  ](#authenticating-and-enriching-channel-membership)
3. [  03   Typed Broadcast Events  ](#typed-broadcast-events)
4. [  04   Handling Reconnects Without Ghost Members  ](#handling-reconnects-without-ghost-members)
5. [  05   Scaling Across Multiple Reverb Workers  ](#scaling-across-multiple-reverb-workers)
6. [  06   Key Takeaways  ](#key-takeaways)

 Presence Channels in Laravel Reverb: Per-User State and Typed Events
--------------------------------------------------------------------

Most Reverb tutorials stop at public and private channels. Presence channels are where real collaborative features live — shared cursors, live user lists, typing indicators — and they demand a more disciplined approach to state, typing, and reconnection.

### Authenticating and Enriching Channel Membership

Presence channels authenticate via `BroadcastServiceProvider` just like private channels, but the return value matters: whatever you return from the closure becomes the member's metadata broadcast to every subscriber.

```php
// routes/channels.php
Broadcast::channel('document.{documentId}', function (User $user, int $documentId): array|false {
    $document = Document::find($documentId);

    if (! $document || ! $user->can('view', $document)) {
        return false;
    }

    return [
        'id'     => $user->id,
        'name'   => $user->name,
        'avatar' => $user->avatar_url,
        'role'   => $document->roleFor($user),
    ];
});

```

Reverb stores this payload in its internal channel registry (backed by Redis in multi-worker deployments). Every `here`, `joining`, and `leaving` event on the client receives this shape — so keep it lean and serialisable.

### Typed Broadcast Events

Avoid stringly-typed payloads. Define a dedicated event class per domain action:

```php
final class CursorMoved implements ShouldBroadcastNow
{
    use Dispatchable, InteractsWithSockets;

    public function __construct(
        public readonly int    $userId,
        public readonly string $documentId,
        public readonly float  $x,
        public readonly float  $y,
    ) {}

    public function broadcastOn(): array
    {
        return [new PresenceChannel("document.{$this->documentId}")];
    }

    public function broadcastAs(): string
    {
        return 'cursor.moved';
    }

    /** Exclude the sender from receiving their own event. */
    public function broadcastToEveryoneElse(): static
    {
        return $this->dontBroadcastToCurrentUser();
    }
}

```

Using `broadcastAs()` gives you a clean contract between server and client. On the Echo side:

```javascript
window.Echo.join(`document.${documentId}`)
    .here(members  => store.setMembers(members))
    .joining(member => store.addMember(member))
    .leaving(member => store.removeMember(member))
    .listen('.cursor.moved', ({ userId, x, y }) => {
        cursors.update(userId, x, y);
    });

```

Note the leading dot in `.cursor.moved` — required when you override `broadcastAs()`.

### Handling Reconnects Without Ghost Members

When a client drops and reconnects, Reverb fires `leaving` then `joining` in sequence. If your server-side state (e.g., a Redis hash of active collaborators) is only updated via those events, a crash loop creates ghost entries.

Mitigate this with a heartbeat approach:

```php
// In a Livewire component or dedicated endpoint
public function heartbeat(string $documentId): void
{
    $key = "presence:document:{$documentId}:user:{$this->userId}";
    Redis::setex($key, 30, now()->timestamp);
}

```

A scheduled command (every minute) prunes keys older than the TTL, keeping your membership list consistent independently of WebSocket events.

### Scaling Across Multiple Reverb Workers

Reverb uses Redis pub/sub to synchronise presence state across workers. Ensure your `config/reverb.php` points to a dedicated Redis connection — do not share it with your queue connection:

```php
'servers' => [
    'reverb' => [
        'driver'     => 'reverb',
        'connection' => env('REVERB_REDIS_CONNECTION', 'reverb'),
    ],
],

```

```php
// config/database.php  (redis connections)
'reverb' => [
    'url'      => env('REVERB_REDIS_URL'),
    'host'     => env('REVERB_REDIS_HOST', '127.0.0.1'),
    'password' => env('REVERB_REDIS_PASSWORD'),
    'port'     => env('REVERB_REDIS_PORT', '6379'),
    'database' => env('REVERB_REDIS_DB', '1'),
],

```

Isolating the connection prevents queue-heavy workloads from starving the pub/sub pipeline.

### Key Takeaways

- Return a structured array from channel auth closures — it becomes the member metadata shape for all clients.
- Use dedicated event classes with `broadcastAs()` for a typed, versioned contract between PHP and JavaScript.
- Call `dontBroadcastToCurrentUser()` on high-frequency events (cursors, keystrokes) to halve unnecessary traffic.
- Supplement WebSocket membership events with a Redis TTL heartbeat to survive reconnect storms.
- Isolate Reverb's Redis connection from your queue connection to prevent throughput contention at scale.

 Found this useful?

          [  ](https://twitter.com/intent/tweet?url=https%3A%2F%2Fmsaied.com%2Farticles%2Flaravel-reverb-building-presence-channels-with-per-user-state-and-typed-events&text=Laravel+Reverb%3A+Building+Presence+Channels+with+Per-User+State+and+Typed+Events) [  ](https://www.linkedin.com/sharing/share-offsite/?url=https%3A%2F%2Fmsaied.com%2Farticles%2Flaravel-reverb-building-presence-channels-with-per-user-state-and-typed-events) 

 Frequently Asked Questions 
----------------------------

  3 questions  

     Q01  Does Laravel Reverb support presence channels out of the box, or do I need extra packages?        Reverb supports presence channels natively via Laravel Echo and the standard `PresenceChannel` class. No additional packages are required beyond `laravel/reverb` and `laravel-echo`. 

      Q02  Why use `ShouldBroadcastNow` instead of `ShouldBroadcast` for cursor events?        `ShouldBroadcastNow` bypasses the queue and broadcasts synchronously, which is critical for low-latency events like cursor movements. Queued broadcasting adds unpredictable delay that makes real-time UI feel broken. 

      Q03  How do I prevent a user's own cursor event from echoing back to them?        Call `$this-&gt;dontBroadcastToCurrentUser()` inside the event or expose a `broadcastToEveryoneElse()` helper that returns it. Reverb will exclude the socket that triggered the broadcast from receiving the event. 

  Continue reading

 More Articles 
---------------

 [ View all    ](https://msaied.com/articles) 

 [ ![Laravel Lock: Distributed Locks for Models and Routes](https://cdn.msaied.com/562/7649de72113e99332a9f7e25015f9397.png) Laravel Distributed Locks Composer Package 

### Laravel Lock: Distributed Locks for Models and Routes

Laravel Lock is a package by Md Mahedi Zaman Zaber that wraps distributed locking behind a fluent builder, a H...

  ![Mohamed Said](https://cdn.msaied.com/01KT78WE565VEMM3PSNQAAB0MJ.jpg)  Mohamed Said 

 17 Aug 2026     4 min read  

  Read    

 ](https://msaied.com/articles/laravel-lock-distributed-locks-for-models-and-routes) [ ![How Two Non-Developers Built laracon.us/photos with Claude, Laravel, and Laravel Cloud](https://cdn.msaied.com/561/aa231bfcc6b487d352abf7be487875d3.png) Laravel Cloud Claude AI AI-assisted development 

### How Two Non-Developers Built laracon.us/photos with Claude, Laravel, and Laravel Cloud

Laravel's field marketers built a real, production photo-sharing app for Laracon US 2026 using Claude AI, the...

  ![Mohamed Said](https://cdn.msaied.com/01KT78WE565VEMM3PSNQAAB0MJ.jpg)  Mohamed Said 

 17 Aug 2026     4 min read  

  Read    

 ](https://msaied.com/articles/how-two-non-developers-built-laraconusphotos-with-claude-laravel-and-laravel-cloud) [ ![Job Batching with Laravel Concurrency: Parallel Work Without the Chaos](https://cdn.msaied.com/559/d55e86a2ecbe32de5e2196f5be63511d.png) laravel concurrency queues 

### Job Batching with Laravel Concurrency: Parallel Work Without the Chaos

Learn how to combine Laravel's Concurrency facade with job batching to run parallel workloads safely, avoid sh...

  ![Mohamed Said](https://cdn.msaied.com/01KT78WE565VEMM3PSNQAAB0MJ.jpg)  Mohamed Said 

 17 Aug 2026     4 min read  

  Read    

 ](https://msaied.com/articles/job-batching-with-laravel-concurrency-parallel-work-without-the-chaos) 

   [  ![Mohamed Said](https://cdn.msaied.com/01KT78WE565VEMM3PSNQAAB0MH.png)   Mohamed Said Laravel Backend Engineer  ](https://msaied.com)Senior Backend Engineer specializing in Laravel, scalable SaaS platforms, APIs, and cloud infrastructure. I build secure, high-performance web applications that help businesses grow.

Explore

- [Home](https://msaied.com)
- [Projects](https://msaied.com/projects)
- [Articles](https://msaied.com/articles)
- [Certificates](https://msaied.com/certificates)
- [Contact](https://msaied.com#contact-section)

Connect

- [   hello@msaied.com ](mailto:hello@msaied.com)
- [   +20 109 461 9204 ](tel:+201094619204)

© 2026 Mohamed Said. All rights reserved.

 [  ](https://github.com/EG-Mohamed) [  ](https://www.linkedin.com/in/msaiedm/) [  ](https://wa.me/201094619204) [  ](mailto:hello@msaied.com) [  ](https://drive.google.com/file/u/0/d/1MF20IPRJyzfy32mhEutjL5EpSls0w2Q8/view)
