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/authendpoint signs it with your secret and Foony verifies the signature, so your existing channel authorization callbacks inroutes/channels.phpkeep 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 andmember_added/member_removedas 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.