WebSocket
Overview
Pogo WebSocket embeds a Pusher-compatible WebSocket server into FrankenPHP.
It provides:
- a Caddy HTTP handler for WebSocket connections,
- PHP native publish functions,
- a Laravel broadcasting driver,
- private and presence channel auth through a dedicated FrankenPHP auth worker,
- optional Redis Pub/Sub for multi-node fanout.
Status and fit
Pogo WebSocket is experimental. It is suitable for demos, local testing, and controlled evaluation of a FrankenPHP-native realtime runtime.
Use it when:
- You want to evaluate Pusher-style broadcasting without a separate Node.js or hosted realtime service.
- Your client can use Laravel Echo or the Pusher protocol subset.
- At-most-once realtime delivery is acceptable.
Avoid presenting it as a drop-in production replacement for Laravel Reverb, Pusher, or hosted realtime systems until you validate behavior, benchmarks, and failure modes for your topology.
Supported protocol behavior includes connection establishment, ping/pong, public/private/presence subscriptions, client events on private and presence channels, and pusher:signin.
Build
Compile the module into FrankenPHP:
xcaddy build \
--with github.com/dunglas/frankenphp@v1.12.3 \
--with github.com/dunglas/frankenphp/caddy@v1.12.3 \
--with github.com/y-l-g/websocket/module@main
Install the Laravel driver:
composer require pogo/websocket
php artisan pogo:ws-install
Caddy configuration
{
frankenphp {
worker {
file public/frankenphp-worker.php
}
}
order pogo_websocket before php_server
}
:8080 {
route /app/* {
pogo_websocket {
app_id pogo-app
app_secret {$WS_APP_SECRET}
auth_path /pogo/auth
auth_script public/websocket-worker.php
webhook_secret {$POGO_WEBHOOK_SECRET}
allowed_origins https://app.example.com https://admin.example.com
handshake_rate 100
handshake_burst 50
max_connections 10000
max_auth_body 16384
max_concurrent_auth 100
broker_queue_size 1024
shard_queue_size 1024
num_workers 2
num_shards 8
ping_period 54s
pong_wait 60s
write_wait 10s
shutdown_timeout 10s
# redis_host redis:6379
# redis_password {$REDIS_PASSWORD}
# redis_db 0
# redis_tls false
}
}
route {
root * public
encode zstd br gzip
php_server {
index frankenphp-worker.php
try_files {path} frankenphp-worker.php
resolve_root_symlink
}
}
}
By default, WebSocket upgrades accept requests without an Origin header and browser requests whose Origin host matches the request host. Configure allowed_origins when your frontend connects from a different origin.
Application integration
Set Laravel environment variables:
BROADCAST_CONNECTION=pogo
WS_APP_ID=pogo-app
WS_APP_SECRET=change-me-to-a-long-random-secret
POGO_WEBHOOK_SECRET=change-me-to-a-different-random-secret
VITE_POGO_APP_KEY="${WS_APP_ID}"
VITE_POGO_HOST=localhost
VITE_POGO_PORT=80
VITE_POGO_WSS_PORT=443
Configure the broadcasting connection:
'pogo' => [
'driver' => 'pogo',
'app_id' => env('WS_APP_ID'),
'secret' => env('WS_APP_SECRET'),
],
Use Laravel Echo with the Pusher client:
import Echo from 'laravel-echo'
import Pusher from 'pusher-js'
window.Pusher = Pusher
window.Echo = new Echo({
broadcaster: 'pusher',
key: import.meta.env.VITE_POGO_APP_KEY || 'pogo-app',
cluster: 'mt1',
wsHost: import.meta.env.VITE_POGO_HOST || window.location.hostname,
wsPort: import.meta.env.VITE_POGO_PORT || 80,
wssPort: import.meta.env.VITE_POGO_WSS_PORT || 443,
forceTLS: false,
disableStats: true,
enabledTransports: ['ws', 'wss'],
authEndpoint: '/pogo/auth',
userAuthentication: {
endpoint: '/pogo/user-auth'
}
})
PHP API
pogo_websocket_publish(string $appId, string $channel, string $event, string $data): int;
pogo_websocket_broadcast_multi(string $appId, string $channels, string $event, string $data): int;
Return status codes:
| Code | Meaning |
|---|---|
0 | Success |
1 | Hub missing |
2 | Channel too long |
3 | Event too long |
4 | Payload too large |
5 | Invalid payload JSON |
6 | Broker publish failed |
7 | Invalid multi-channel JSON |
8 | Broker queue full |
9 | Shard queue full |
The Laravel broadcaster converts native failures into BroadcastException.
Operations
- Use strong, different values for
WS_APP_SECRETandPOGO_WEBHOOK_SECRET. - Set
allowed_originsfor browser clients that connect from another origin. - Enforce per-client connection limits at the reverse proxy if FrankenPHP is behind a proxy that hides client IPs.
- Use Redis Pub/Sub only for best-effort multi-node fanout; messages are not persisted, replayed, or acknowledged.
- Prometheus metrics are exposed through Caddy admin metrics at
/metrics.
Troubleshooting
- 4100 over capacity: increase
max_connectionsor reduce client count. - 4009 connection unauthorized: verify
app_id,app_secret, andWS_APP_SECRET. - Too many requests: tune
handshake_rateandhandshake_burst. - Private or presence auth fails: confirm
/pogo/authand/pogo/user-authare reachable and protected by normal Laravel authentication.