Reverb
Add Reverb for WebSocket broadcasting. Run commands in the Laravel application.
The connection setup is shared across frontend stacks. The counter below is a Livewire 4 / Blade example, not a Svelte component; use it only in an application that already has Livewire and its assets configured.
Install
bash
php artisan install:broadcasting --reverbAccept installation of Reverb and the Node dependencies. Check config/broadcasting.php, config/reverb.php, routes/channels.php, and the channels registration in bootstrap/app.php.
The installer also configures Echo. Inspect the generated frontend changes before adding another client; the plain-JavaScript setup below uses laravel-echo and pusher-js.
Environment
Keep real app secrets only in .env; use placeholders in .env.example. Never expose REVERB_APP_SECRET through a VITE_* variable.
Herd Pro
Create/start the Reverb service in Herd, then copy its app credentials, hostname, port, and TLS setting into the application's REVERB_* values. Do not assume port 443 or a fixed set of credentials; use the values for that service.
Herd manages the server process. Do not also run reverb:start on the same port. If using a shared Herd service, its server configuration is managed separately from this application's config/reverb.php.
Manually Managed Server
For an HTTP-only local setup:
dotenv
BROADCAST_CONNECTION=reverb
REVERB_APP_ID=local-app
REVERB_APP_KEY=replace-with-generated-key
REVERB_APP_SECRET=replace-with-generated-secret
REVERB_SERVER_HOST=127.0.0.1
REVERB_SERVER_PORT=8080
REVERB_HOST=localhost
REVERB_PORT=8080
REVERB_SCHEME=httpKeep the credentials generated by the installer rather than copying these placeholders. REVERB_SERVER_* is the bind address; REVERB_HOST / PORT is where Laravel sends broadcasts.
An HTTPS page needs wss, not this HTTP configuration. For a secured Herd/Valet site, use the site's certificate and hostname as described in the Reverb TLS setup.
Browser Settings
Use these for either server option:
dotenv
BROADCAST_CONNECTION=reverb
VITE_REVERB_APP_KEY="${REVERB_APP_KEY}"
VITE_REVERB_HOST="${REVERB_HOST}"
VITE_REVERB_PORT="${REVERB_PORT}"
VITE_REVERB_SCHEME="${REVERB_SCHEME}"After changing values, clear config and restart Vite or rebuild assets:
bash
php artisan config:clear
npm run buildFor a manual server, keep a separate terminal running:
bash
php artisan reverb:start --debugEcho Client
File: resources/js/echo.js
Keep the generated configuration if it already matches:
js
import Echo from 'laravel-echo';
import Pusher from 'pusher-js';
window.Pusher = Pusher;
window.Echo = new Echo({
broadcaster: 'reverb',
key: import.meta.env.VITE_REVERB_APP_KEY,
wsHost: import.meta.env.VITE_REVERB_HOST,
wsPort: import.meta.env.VITE_REVERB_PORT ?? 80,
wssPort: import.meta.env.VITE_REVERB_PORT ?? 443,
forceTLS: (import.meta.env.VITE_REVERB_SCHEME ?? 'https') === 'https',
enabledTransports: ['ws', 'wss'],
});Import it once from the JavaScript entrypoint loaded by the Blade page, unless the generated bootstrap.js already imports it:
js
import './echo';Channels
Keep the generated user-channel authorization in routes/channels.php. The demo uses a public home channel, so it needs no authorization callback.
Public channels are visible to any connected client. Use private channels plus authorization callbacks for user-specific data; allowed_origins is not a replacement for channel authorization.
Counter Smoke Test
This broadcasts the current component value, not a shared database counter. Refreshes start at zero; disconnected clients miss events, and simultaneous clicks can overwrite each other. Keep it as a local transport check. For real shared state, persist atomic updates server-side and reload that state on reconnect.
Event
bash
php artisan make:event HomeCounterUpdatedFile: app/Events/HomeCounterUpdated.php
php
<?php
namespace App\Events;
use Illuminate\Broadcasting\Channel;
use Illuminate\Contracts\Broadcasting\ShouldBroadcastNow;
use Illuminate\Foundation\Events\Dispatchable;
class HomeCounterUpdated implements ShouldBroadcastNow
{
use Dispatchable;
public function __construct(public int $count) {}
/** @return array<int, Channel> */
public function broadcastOn(): array
{
return [new Channel('home')];
}
/** @return array<string, int> */
public function broadcastWith(): array
{
return ['count' => $this->count];
}
}ShouldBroadcastNow sends synchronously, so this demo does not need a queue worker. Normal ShouldBroadcast events do.
Livewire Component
bash
php artisan make:livewire home-counter --sfcFile: resources/views/components/⚡home-counter.blade.php (default Livewire 4 location)
blade
<?php
use App\Events\HomeCounterUpdated;
use Livewire\Attributes\On;
use Livewire\Component;
new class extends Component
{
// ponytail: transient demo state; use persistent atomic updates for a real shared counter.
public int $count = 0;
public function increment(): void
{
HomeCounterUpdated::dispatch(++$this->count);
}
public function decrement(): void
{
HomeCounterUpdated::dispatch(--$this->count);
}
/** @param array{count: int} $payload */
#[On('echo:home,HomeCounterUpdated')]
public function onCounterUpdated(array $payload): void
{
$this->count = (int) $payload['count'];
}
};
?>
<div class="flex items-center gap-3 rounded-xl border p-6">
<button type="button" wire:click="decrement" aria-label="Decrease count">−</button>
<output aria-live="polite">{{ $count }}</output>
<button type="button" wire:click="increment" aria-label="Increase count">+</button>
</div>Embed in a Blade page that loads the application's JS and Livewire assets:
blade
<livewire:home-counter />Component Check
File: tests/Feature/HomeCounterTest.php
php
<?php
use App\Events\HomeCounterUpdated;
use Illuminate\Support\Facades\Event;
use Livewire\Livewire;
test('the counter broadcasts changes and accepts received values', function () {
Event::fake([HomeCounterUpdated::class]);
Livewire::test('home-counter')
->call('increment')->assertSet('count', 1)
->call('decrement')->assertSet('count', 0)
->call('onCounterUpdated', ['count' => 7])->assertSet('count', 7);
Event::assertDispatched(HomeCounterUpdated::class, fn ($event) => $event->count === 1);
Event::assertDispatched(HomeCounterUpdated::class, fn ($event) => $event->count === 0);
});bash
php artisan test --compact tests/Feature/HomeCounterTest.phpThis checks component logic only; it fakes the event, so it does not verify WebSocket delivery.
Verify Delivery
- Start Vite and the Reverb service, then open the counter page in two windows.
- Wait for both WebSocket connections, then click in one window at a time. Both counts should update.
- If not, check the WebSocket handshake, Reverb logs, matching app credentials, host/port, TLS, and server
allowed_origins. - After changing
VITE_REVERB_*, restart Vite; after changing manual-server config, restart Reverb too.
Production
Use real app credentials, restrict allowed_origins, and terminate TLS at the public WebSocket endpoint. Supervise reverb:start and the queue worker separately; allow proxy traffic to both /app and /apps.
Restart the supervised server after deployment:
bash
php artisan reverb:restart