Docs

Pusher and Laravel Echo

Point Laravel Echo and any Pusher client at Foony with only environment variables. Public, private, and presence channels, server broadcasting, and client events all work unchanged.

Foony speaks the Pusher protocol. Laravel Echo, pusher-js, and Laravel’s server side broadcasting talk to Foony with no code changes, so you can pair Laravel Cloud with Foony instead of running your own WebSocket cluster. You set a handful of environment variables and keep writing broadcast() and Echo.private() exactly as you do today.

This surface is a compatibility layer over the same edge that serves the native SDKs. Every Pusher message is metered, quota checked, and fanned out the same way, so a channel behaves identically whether a Pusher client or a native client is attached.

Your credentials

A Pusher app is three values. Map them to a Foony app and one of its API keys:

Pusher variable Foony value
PUSHER_APP_ID Your app slug, for example acme-chat
PUSHER_APP_KEY An API key’s public id, for example kid_1a2b3c...
PUSHER_APP_SECRET That key’s secret
PUSHER_HOST realtime.foony.io
PUSHER_PORT 443
PUSHER_SCHEME https

These are not three separate secrets. A Foony API key is a single string, appSlug.keyId:secret (exactly what the dashboard shows), and the three Pusher values are just its parts: app_id is the slug, key is the key id, secret is the secret.

Create the API key in the dashboard under your app’s Keys tab. The key’s capability is enforced on every Pusher subscribe and publish, so a key scoped to chat:* still only reaches those channels. For a drop in Pusher replacement, use a key with full access.

The secret never leaves your backend. Foony verifies the channel authentication and HTTP request signatures with it offline, the same way the official Pusher servers do.

Laravel

Add the variables to .env:

BROADCAST_CONNECTION=pusher

PUSHER_APP_ID=acme-chat
PUSHER_APP_KEY=kid_1a2b3c4d5e6f7g8h
PUSHER_APP_SECRET=your-key-secret
PUSHER_HOST=realtime.foony.io
PUSHER_PORT=443
PUSHER_SCHEME=https
PUSHER_APP_CLUSTER=mt1

Laravel already ships a pusher broadcast connection. It reads the host options from the same variables, so no change to config/broadcasting.php is needed on a current Laravel. If your config predates host overrides, set them explicitly:

'pusher' => [
    'driver' => 'pusher',
    'key' => env('PUSHER_APP_KEY'),
    'secret' => env('PUSHER_APP_SECRET'),
    'app_id' => env('PUSHER_APP_ID'),
    'options' => [
        'host' => env('PUSHER_HOST'),
        'port' => env('PUSHER_PORT', 443),
        'scheme' => env('PUSHER_SCHEME', 'https'),
        'useTLS' => true,
    ],
],

Point Laravel Echo at the same host in resources/js/bootstrap.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,
  wsHost: 'realtime.foony.io',
  wsPort: 443,
  wssPort: 443,
  forceTLS: true,
  enabledTransports: ['ws', 'wss'],
});

Channels

The three Pusher channel types work as written. Foony reads the private- and presence- prefixes and applies the same rules Pusher does.

  • Public channels need no authentication. Subscribe with Echo.channel('orders') and broadcast to it from the server.
  • Private channels (private-) require a signed subscription. Laravel’s /broadcasting/auth endpoint signs it with your secret and Foony verifies the signature, so your existing channel authorization callbacks in routes/channels.php keep working unchanged.
  • Presence channels (presence-) add member data. Return the member from your channel authorization callback as usual. Foony delivers the current member list on subscribe and member_added / member_removed as people come and go. A user with several tabs counts once, and leaves only when the last tab closes.

Broadcasting from the server

broadcast() and event() publish over Foony’s HTTP API automatically. toOthers() is supported: the originating client is excluded even when it is connected to a different edge server.

broadcast(new OrderShipped($order))->toOthers();

Batches are supported too, so a single request can fan several events to different channels.

Client events

Client events (client- prefixed) let one browser message the others without a server round trip, which is what Laravel Echo’s whisper() uses:

Echo.private(`room.${roomId}`)
  .whisper('typing', { userId })
  .listenForWhisper('typing', (event) => {
    showTypingIndicator(event.userId);
  });

Client events are allowed only on private and presence channels, and only when the key may publish there. They are live only and are never stored.

Any Pusher client

Nothing here is Laravel specific. Point pusher-js directly at Foony:

import Pusher from 'pusher-js';

const pusher = new Pusher('kid_1a2b3c4d5e6f7g8h', {
  wsHost: 'realtime.foony.io',
  wsPort: 443,
  forceTLS: true,
  authEndpoint: '/your-backend/pusher/auth',
});

const channel = pusher.subscribe('orders');
channel.bind('OrderShipped', (data) => {
  console.log(data);
});

What is supported

Feature Supported
Public, private, and presence channels Yes
Server broadcasting (/apps/{id}/events, batch) Yes
toOthers() socket exclusion Yes
Client events (whisper) Yes
Channel occupancy and presence user lookups Yes
Webhooks (channel_occupied, member_added, …) Not yet
End to end encrypted channels (private-encrypted-) Not yet

Foony does not persist Pusher events, matching Pusher itself. If you want stored history and replay on reconnect, use a native SDK and the history API alongside the Pusher surface. Both reach the same channels.