A small Elixir server that speaks the Pusher Channels WebSocket and HTTP protocol. Use it as a self-hosted replacement for Laravel Reverb, Soketi or Pusher itself: Laravel's built-in pusher broadcaster and laravel-echo / pusher-js talk to it unmodified.

It ships two ways from the same code:

  • Standalone: a Docker image you run next to your app.
  • Embedded: a Hex dependency you add to your own supervision tree.

Runs on one server with no other services, or on several servers sharing a Redis. Redis is optional.

Features

  • Public, private and presence channels, with HMAC-SHA256 channel auth
  • Presence deduplicated per user across tabs (member_added / member_removed fire on first join / last leave)
  • Client events (client-*) on private and presence channels
  • REST API signed like Pusher's: trigger events, batch events, list channels, channel info
  • Signed webhooks (channel_occupied, channel_vacated, member_added, member_removed, client_event) with retries
  • Optional rate limits and connection cap, and Prometheus metrics
  • Multi-server mode through Redis (optional)
  • Graceful shutdown: clients are told to reconnect before the node exits
  • Several independent instances in one app (different app ids, different ports)

Quickstart: Docker + Laravel

docker build -t pusher-server .
docker run -p 6001:6001 \
  -e PUSHER_APP_ID=my-app-id \
  -e PUSHER_APP_KEY=my-app-key \
  -e PUSHER_APP_SECRET=a-long-random-secret \
  pusher-server

Point Laravel at it in .env:

BROADCAST_CONNECTION=pusher
PUSHER_APP_ID=my-app-id
PUSHER_APP_KEY=my-app-key
PUSHER_APP_SECRET=a-long-random-secret
PUSHER_HOST=127.0.0.1
PUSHER_PORT=6001
PUSHER_SCHEME=http

The full Laravel and Echo setup is in the Getting started guide.

Quickstart: embed in your Elixir app

# mix.exs
{:pusher_server, "~> 0.1"}

# lib/my_app/application.ex
children = [
  MyApp.Repo,
  MyAppWeb.Endpoint,
  {PusherServer,
   name: MyApp.PusherServer,
   port: 6001,
   app_key: "my-key",
   app_secret: "my-secret",
   app_id: "my-app-id"}
]

Every option falls back to the same environment variables the standalone image uses. The server runs its own listener on its own port; it does not live inside your Phoenix endpoint. See the Embedding guide.

Several servers

Set the same app credentials and the same PUSHER_REDIS_URL on every node and put a load balancer in front. Events, presence and occupancy are then shared across nodes. Without PUSHER_REDIS_URL no Redis connection is ever opened. See the Multi-server guide.

Documentation

Status

Early (0.x). The test suite starts real nodes on real sockets, speaks the WebSocket protocol to them, signs REST calls the way Laravel does, and, with a real Redis, covers cross-node delivery, a node dying, Redis being flushed and Redis being unreachable.

Not verified yet: behaviour at load, and Elixir versions other than the one CI uses. The batch, channel-list, retry, limit and metrics features are covered by tests only. Please test your own setup before relying on it, and report what you find.

Not implemented: user sign-in features and the users endpoints, durable webhook retries, per-IP limits, built-in TLS, Redis Cluster. Delivery is at-most-once, like Pusher. Details in Protocol support.

Development and contributing

mix deps.get
mix test            # Redis tests are skipped unless PUSHER_TEST_REDIS_URL is set
iex -S mix          # starts the listener on PORT (default 6001)

See CONTRIBUTING.md. Security issues: see SECURITY.md. Changes are listed in CHANGELOG.md.

License

MIT, see LICENSE.