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) 

 [ ![Cursor Pagination, Chunked Iteration, and Lazy Collections at Scale in Laravel](https://cdn.msaied.com/355/3a6df23a2c16b740843260134fad7c63.png) laravel eloquent performance 

### Cursor Pagination, Chunked Iteration, and Lazy Collections at Scale in Laravel

Offset pagination breaks under large datasets. Learn how cursor pagination, chunked iteration, and lazy collec...

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

 3 Jul 2026     4 min read  

  Read    

 ](https://msaied.com/articles/cursor-pagination-chunked-iteration-and-lazy-collections-at-scale-in-laravel-1) [ ![Job Batching, Chaining, and Rate-Limited Middleware in Laravel Queues](https://cdn.msaied.com/353/89d47dc6b618d5435f9d7f333b75e922.png) laravel queues jobs 

### Job Batching, Chaining, and Rate-Limited Middleware in Laravel Queues

Go beyond basic dispatch: learn how to compose Laravel job batches with callbacks, chain dependent jobs safely...

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

 3 Jul 2026     3 min read  

  Read    

 ](https://msaied.com/articles/job-batching-chaining-and-rate-limited-middleware-in-laravel-queues-2) [ ![Laravel Caching Strategies: Tags, Stampede Prevention, and Cache-Aside at Scale](https://cdn.msaied.com/351/ac186f395c8b0f7ae2e8ff230c61e323.png) laravel caching performance 

### Laravel Caching Strategies: Tags, Stampede Prevention, and Cache-Aside at Scale

Beyond simple remember() calls: how to use cache tags for granular invalidation, prevent cache stampedes with...

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

 3 Jul 2026     3 min read  

  Read    

 ](https://msaied.com/articles/laravel-caching-strategies-tags-stampede-prevention-and-cache-aside-at-scale-1) 

   [  ![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)
