# Listen for Laravel events in Vue with Reverb

> Install Reverb, broadcast a public event through the queue, and subscribe from a Vue page with useEchoPublic.

- URL: https://laravel-vuejs.space/laravel-reverb-vue
- Author: Anass Ez-zouaine (https://laravel-vuejs.space/author/anass-ez-zouaine)
- Published: 2026-10-10
- Topics: Laravel, VueJs

A new post lands and everyone else’s list stays stale until they refresh. Reverb pushes it to every open page. You need three moving parts running (queue, Reverb, Vite) and one composable; here they are.

> **TL;DR**
>
> -   `php artisan install:broadcasting --reverb`, then restart Vite.
> -   Run a queue worker **and** `php artisan reverb:start`; broadcasts are queued.
> -   Implement `ShouldBroadcast`, return a `Channel`, dispatch after the row exists.
> -   Listen with `useEchoPublic('posts', 'PostPublished', ...)`; `useEcho` is for private channels.
>
> **What you'll learn**
>
> -   How to install Reverb and Echo
> -   How to broadcast an event
> -   How to listen with `useEchoPublic` and debug silence
>
> **Who it's for:** Laravel + Vue developers adding real-time updates
>
> **Applies to:** Laravel 13 · Reverb · @laravel/echo-vue · Vue 3

Polling the [posts list](https://laravel-vuejs.space/laravel-inertia-vue-crud) every few seconds works until it does not. Reverb is Laravel’s WebSocket server. Echo is the client. A Vue page can subscribe with `useEchoPublic` from `@laravel/echo-vue` and update when a PHP event fires.

The official write-up is the [broadcasting docs](https://laravel.com/docs/13.x/broadcasting). This page is the short path for a Vue starter kit.

1. **Install** broadcasting with Reverb.
2. **Run** the queue worker and Reverb.
3. **Broadcast** an event.
4. **Listen** on the Vue page.

## Install

```bash
php artisan install:broadcasting --reverb
```

That installs Reverb, the Echo packages, and the `VITE_REVERB_*` keys. Restart Vite after it changes `.env`, or the browser will still see empty keys.

Broadcasts are queued. The [setup guide](https://laravel-vuejs.space/application-setup) starts a queue worker with `composer run dev`. If you are not using that script, run `php artisan queue:work` yourself. Then start the socket server:

```bash
php artisan reverb:start
```

> **Gotcha: two processes, two silent failures**
>
> Leave both running. An event with no worker sits in the `jobs` table. An event with no Reverb process never reaches the browser.

## The event

```bash
php artisan make:event PostPublished
```

`app/Events/PostPublished.php`
```php
namespace App\Events;

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

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

    public function __construct(public Post $post) {}

    public function broadcastOn(): Channel
    {
        return new Channel('posts');
    }
}
```

`ShouldBroadcast` is the line that sends it. A public `Channel` needs no auth. Use `PrivateChannel` when only a logged-in user should hear it, and authorize that name in `routes/channels.php`.

Dispatch it after the row exists:

```php
PostPublished::dispatch($post);
```

> **Heads up: public properties are public**
>
> Put that in `store` after `Post::create()`. The queued job serializes the model. Keep the payload small. Echo will hand the public properties to the browser, so hide anything you would not print on the page.

## The Vue page

`resources/js/pages/posts/Index.vue`
```vue
<script setup lang="ts">
import { ref } from "vue";
import { useEchoPublic } from "@laravel/echo-vue";

const titles = ref<string[]>([]);

useEchoPublic(
  "posts",
  "PostPublished",
  (event: { post: { title: string } }) => {
    titles.value.unshift(event.post.title);
  },
);
</script>

<template>
  <ul>
    <li v-for="title in titles" :key="title">{{ title }}</li>
  </ul>
</template>
```

**Listens on private-posts, misses the event:**

`resources/js/pages/posts/Index.vue`
```ts
useEcho("posts", "PostPublished", onPublished);
```

**Matches the public Channel:**

`resources/js/pages/posts/Index.vue`
```ts
useEchoPublic("posts", "PostPublished", onPublished);
```

`useEchoPublic` subscribes to the public channel when the component is set up and leaves it when the component goes away. `useEcho` is the private-channel hook, so it would listen on `private-posts` and miss this event. The event name matches the class name, `PostPublished`, unless you set `broadcastAs()`.

> **Two windows**
>
> Create a post from another window. The title should appear without a reload. If it does not, check the queue worker, then Reverb, then the browser console for a failed WebSocket. A missing `VITE_REVERB_APP_KEY` fails quietly until you look.

## Private channels and a separate SPA

An Inertia app on the same origin can use the defaults `install:broadcasting` wrote. A Vue SPA on another port is the Sanctum case. Private channels then authorize through `/api/broadcasting/auth`, and Echo needs a custom authorizer that uses the credentialed Axios instance. The setup is in the [Sanctum broadcasting section](https://laravel.com/docs/13.x/sanctum#authorizing-private-broadcast-channels) and depends on the cookie login from [the Sanctum guide](https://laravel-vuejs.space/laravel-sanctum-vue-spa).

> **Gotcha**
>
> Do not point that authorizer at a bearer token you stored for your own SPA. Fix the cookie first.

## Production

Reverb is a process, like the queue worker. Laravel Cloud can run it as a managed WebSocket cluster and injects the `REVERB_*` variables. On a VPS you run `php artisan reverb:start` under a supervisor and proxy the socket port. The Vue bundle only needs the `VITE_REVERB_*` values from the production env at build time. The [deploy guide](https://laravel-vuejs.space/deploy-laravel-vue) covers the asset build. It does not start Reverb for you.

## Recap

- Reverb is the server, Echo the client, the queue the courier.
- Restart Vite after `.env` changes.
- Public channel ↔ `useEchoPublic`; private channel ↔ `useEcho` + `channels.php`.
- In production, Reverb is another supervised process.

## FAQ

**Q: Why does my Laravel Reverb event never reach Vue?**

Check, in order: a queue worker is running, `reverb:start` is running, and the browser has a `VITE_REVERB_APP_KEY` (restart Vite after `.env` changes).

**Q: What is the difference between useEcho and useEchoPublic?**

`useEchoPublic` listens on a public channel. `useEcho` listens on a private channel, so it subscribes to `private-…` and misses public events.

**Q: Do broadcasts need a queue worker?**

Yes. `ShouldBroadcast` events are queued; without a worker they wait in the `jobs` table.

## Next steps

- [Queue a broadcast instead of blocking the save](https://laravel-vuejs.space/laravel-should-broadcast-vue)
- [Laravel database notifications on a Vue page](https://laravel-vuejs.space/laravel-vue-notifications)
- [Deploy a Laravel + Vue app](https://laravel-vuejs.space/deploy-laravel-vue)

## Sources

- [Laravel 13: Broadcasting](https://laravel.com/docs/13.x/broadcasting)
- [Laravel 13: Reverb](https://laravel.com/docs/13.x/reverb)
- [Laravel 13: Sanctum, authorizing private broadcast channels](https://laravel.com/docs/13.x/sanctum#authorizing-private-broadcast-channels)
