← Use cases

Laravel Echo without running Reverb

Change a few .env values; events, channels, whispers and toOthers() keep working.

Laravel Broadcasting already speaks the Pusher protocol, so a Laravel app can send its broadcasts to Hibira with the built-in pusher connection. Your events, channel routes and Echo code stay as they are. What goes away is the WebSocket server you would otherwise run yourself: a long-running Reverb process, Redis to scale it past one machine, sticky load balancing, and someone to watch it at night.

What your users get

  • Order status, notifications and dashboards that update without a refresh.
  • Chat with "typing…" hints and an online list, through Echo's join and whisper.
  • The latest state the moment a page opens, instead of an empty screen until the next event.

Built with

  • Laravel Broadcasting with the pusher connection and pusher/pusher-php-server.
  • Laravel Echo with pusher-js in the browser.
  • Private and presence channels, authorized by your own routes/channels.php.
  • Client events (Echo whisper) and socket exclusion (toOthers()).
  • Cache channels (cache-), which hand the last event to every new subscriber.

Step 1: point Broadcasting at Hibira

On Laravel 11 and later, install broadcasting and choose Pusher when asked:

php artisan install:broadcasting
composer require pusher/pusher-php-server
npm install --save-dev laravel-echo pusher-js

Then set the connection in .env. The values come from your app page:

BROADCAST_CONNECTION=pusher
PUSHER_APP_ID=YOUR_APP_ID
PUSHER_APP_KEY=YOUR_APP_KEY
PUSHER_APP_SECRET=YOUR_APP_SECRET
PUSHER_HOST=ws.hibira.io
PUSHER_PORT=443
PUSHER_SCHEME=https
PUSHER_APP_CLUSTER=ap

VITE_PUSHER_APP_KEY="${PUSHER_APP_KEY}"
VITE_PUSHER_HOST="${PUSHER_HOST}"
VITE_PUSHER_PORT="${PUSHER_PORT}"
VITE_PUSHER_SCHEME="${PUSHER_SCHEME}"

Laravel 10 and earlier use BROADCAST_DRIVER=pusher instead of BROADCAST_CONNECTION. The default config/broadcasting.php already passes the host, port and scheme to the Pusher library; if yours was written by hand, check that its options read them, so nothing points at pusher.com.

Step 2: configure Echo

// resources/js/echo.js
import Echo from 'laravel-echo';
import Pusher from 'pusher-js';
window.Pusher = Pusher;

window.Echo = new Echo({
  broadcaster: 'pusher',
  key: import.meta.env.VITE_PUSHER_APP_KEY,
  cluster: 'ap',
  wsHost: import.meta.env.VITE_PUSHER_HOST,
  wsPort: import.meta.env.VITE_PUSHER_PORT,
  wssPort: import.meta.env.VITE_PUSHER_PORT,
  forceTLS: import.meta.env.VITE_PUSHER_SCHEME === 'https',
  enabledTransports: ['ws', 'wss'],
});

Step 3: broadcast an event to one customer

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

    public function __construct(public Order $order) {}

    public function broadcastOn(): array
    {
        return [new PrivateChannel('orders.'.$this->order->id)];
    }
}

// routes/channels.php
Broadcast::channel('orders.{id}', fn ($user, $id) => $user->orders()->whereKey($id)->exists());

// Wherever the order ships
OrderShipped::dispatch($order);
Echo.private(`orders.${orderId}`)
  .listen('OrderShipped', (e) => showStatus(e.order));

ShouldBroadcast events go through your queue, so a worker (php artisan queue:work) must be running. Use ShouldBroadcastNow to publish during the request when the project has no queue.

Step 4: presence, whispers and toOthers()

Echo.join(`room.${roomId}`)
  .here((users) => online.set(users))
  .joining((user) => online.add(user))
  .leaving((user) => online.remove(user))
  .listen('MessageSent', (e) => messages.append(e.message))
  .listenForWhisper('typing', (e) => typing.show(e.name, 3000));
broadcast(new MessageSent($message))->toOthers();

Whispers are client events: turn on Allow client events in the app settings. toOthers() skips the sender's own tab using the X-Socket-ID header, which Echo adds to Axios requests by itself; with fetch, send 'X-Socket-ID': Echo.socketId() yourself. The chat use case shows the whole room.

Step 5: the latest state for every new page

Broadcast on a channel whose name starts with cache-, and anyone who subscribes later gets the last event at once (it is kept for 30 minutes):

public function broadcastOn(): array
{
    return [new Channel('cache-scoreboard')];
}
Echo.channel('cache-scoreboard')
  .listen('ScoreUpdated', (e) => board.render(e.scores));

Echo.private('cache-orders.1') works the same way and becomes the private-cache-orders.1 channel.

Coming from Reverb or another Pusher-compatible service

Only the .env values change: events, channel routes and Echo code stay the same, so you can switch one environment at a time and switch back just as easily. The migration page lists what to check, and the Laravel guide has the same settings filled in with your app's values once you sign in.

Tips

  • Create one app per environment (local, staging, production) so test events never reach real users.
  • Presence channels hold up to 100 members. For bigger rooms, see live audiences.
  • Each broadcast counts as one message however many browsers receive it; deliveries and presence updates are free.