Reference
Pusher Channels parity
How FluxyChat maps Pusher Channels concepts onto rooms, JWT auth, Durable Objects, and the SDK.
FluxyChat maps Pusher Channels concepts onto rooms, JWT auth, Cloudflare Durable Objects, and @fluxy-chat/sdk. Use this page when migrating from Pusher or comparing event models.
Concept mapping
| Pusher | FluxyChat |
|---|---|
| Channel | Room (rooms.id) |
| Public channel | Room type: "public" |
| Private channel | Room type: "group" or dm plus membership |
| Presence channel | WS presence events on any room |
| Cache channel | cache=1 on WS plus DO snapshot |
| Encrypted channel | e2eEnabled on room plus SDK AES-GCM |
| User channel | UserDurableObject plus /ws/user/:userId |
socket_id | socketId on subscribe; excludeSocketId on publish |
| HTTP trigger | POST /events (multi-room) |
| Webhooks | Outbound signed webhooks plus verify playground |
Authentication is always JWT (or project API key for server routes). Public rooms do not mean anonymous access: any signed-in project member may join.
Publish, subscribe, and auth
- WebSocket: connect to
/ws/rooms/:roomId?token=… - REST: messages, edits, reactions via standard room APIs
- Auth: JWT with
projectId,userId,roles; membership enforced in Worker and Room DO
Presence and subscription count
WebSocket events (SDK types on FluxyChatClient):
subscription_succeeded: initial presence snapshotmember_joined/member_leftpresence: full member list updatessubscription_count: connection count for the room
Pass optional presenceInfo on connect for user_info-style metadata.
Occupied and vacated webhooks
Configure outbound webhooks for:
room.occupied: first subscriber connectsroom.vacated: last subscriber disconnects
Defaults are available in the admin console webhook UI.
Client events
Clients may send client-* event names over WS (rate-limited). Server broadcasts to other members and can emit client_event webhooks. Use excludeSocketId to skip the sender.
Cache channels
- Connect with
cache=1query param to receive the last cached server event immediately - Room DO stores a small snapshot;
cache_misswebhook when cache is empty on subscribe
Encrypted channels (E2E)
- Owner or admin:
PATCH /rooms/:idwith{ "e2eEnabled": true } - Members:
GET /rooms/:id/e2e-keyreturns{ e2eKey }(server-wrapped at rest) - SDK:
getRoomE2eKey(roomId)and encrypted message envelopes (room-e2e.ts)
TLS still applies; E2E protects payload at the application layer.
| Aspect | Pusher encrypted channels | FluxyChat room E2E |
|---|---|---|
| Key distribution | Client derives shared secret; Pusher never sees plaintext | Room key via GET /rooms/:id/e2e-key (JWT member); key wrapped at rest in D1 |
| Server visibility | Pusher routes ciphertext only | Worker and DO store ciphertext envelopes |
| Channel auth | POST /auth returns signed subscription | POST /auth/channel plus member JWT; optional presenceInfo |
| Rotation | App-managed master secret | Re-enable E2E on room or rotate via admin PATCH /rooms/:id |
Manual E2E checklist:
- Enable E2E on a private room; two members fetch key and exchange encrypted messages. A third member without key sees opaque payload in REST and WS.
- Disable E2E: new messages are plaintext on the wire (TLS only).
- Guest session on a public room must not call
/e2e-key(403) unless promoted to member.
Pusher docs: Encrypted channels. FluxyChat uses the room key API and SDK room-e2e.ts helpers instead of Pusher shared-secret derivation.
User channel
POST /auth/signin: exchange credentials for a user-scoped session (see OpenAPI)- SDK:
signIn(),connectUser(),triggerUserEvent(userId, event) - React:
useUserChannel({ userId }) - Server push:
POST /users/:userId/events(JWT, same user or admin)
Binding: USER Durable Object in wrangler.toml.
Multi-room HTTP trigger
curl -X POST "$WORKER/events" \
-H "Authorization: Bearer $JWT" \
-H "Content-Type: application/json" \
-d '{
"roomIds": ["room-a", "room-b"],
"name": "my-event",
"data": { "score": 1 },
"excludeSocketId": "abc123"
}'SDK: client.triggerEvents({ roomIds, name, data, excludeSocketId })
Exclude sender
On WS connect, server assigns socketId. Pass excludeSocketId (or socket_id) on POST /events and room announce paths to omit the originating connection.
Connection state
SDK emits state_change and exposes connectionState (connecting, connected, reconnecting, disconnected, and so on).
Transport fallbacks
Order: WebSocket, then SSE (/rooms/:id/stream), then polling. See transport fallback.
Webhook batch verification
Verify inbound or test signatures:
- Single payload:
POST /webhooks/verify(registered webhook) orPOST /webhooks/verify-batchwith{ secret, body, signature } - Batch:
{ secret, events: [...], signature }where signature isHMAC-SHA256(secret, JSON.stringify(events)) - Headers:
X-Fluxy-SignatureorX-Pusher-Signature(alias)
Admin Webhook playground supports registered, raw, and batch modes.
Live inspector
Admin console includes a realtime event inspector for debugging WS traffic.
Watchlist
GET/POST/DELETE /users/:userId/watchlist- Fanout: room events matching watchlist targets emit
watchlist_eventon user channel - Dashboard: user watchlist card
Migration: 0042_user_watchlist.sql
Terminate connections
DELETE /users/:userId/connections closes user channel WS and room connections for that user.
SDK: terminateUserConnections(userId)
Global event binding
client.onAnyEvent((type, payload) => { /* bind_global */ });
client.offAnyEvent(handler);
// useChat({ onAnyEvent: ... })Public channels
- Create room with
type: "public"(or patch existing room) - Any authenticated project member may connect; first connect lazy-joins
room_members - Optional stricter limit:
RATE_LIMIT_PUBLIC_WS_CONNECTIONS_PER_MINUTE
Webhook event types
Register client_event, member_joined, member_left, subscription_count, cache_miss, room.occupied, room.vacated, and related types in webhook configuration. Member and subscription webhooks are emitted from Room DO when those WS events occur.
SDK quick reference
import { FluxyChatClient, useChat, useUserChannel } from "@fluxy-chat/sdk";
const client = new FluxyChatClient({ baseUrl, userId, token });
await client.signIn({ /* … */ });
const userConn = client.connectUser();
userConn.on("notification", (p) => {});
await client.triggerEvents({
roomIds: ["a", "b"],
name: "score-update",
data: { n: 1 },
excludeSocketId: conn.socketId,
});
const { e2eKey } = await client.getRoomE2eKey(roomId);
await client.addWatchlistTarget(userId, { type: "room", targetId: roomId });
client.onAnyEvent((type, data) => console.log(type, data));
await client.terminateUserConnections(userId);const { messages, connectionState } = useChat({
roomId,
client,
onAnyEvent: (type, data) => {},
cache: true,
});Deploy checklist
- Apply D1 migrations through
0042(E2E, watchlist, and related tables) - Deploy Worker with
USERbinding and DO migration v3 - Set optional
RATE_LIMIT_PUBLIC_WS_CONNECTIONS_PER_MINUTEin Worker secrets - Redeploy dashboard for admin inspector and webhook playground
OpenAPI: apps/worker/openapi.yaml documents /events, /auth/signin, /users/*, /webhooks/verify-batch, and /rooms/{id}/e2e-key.
Related docs
Embeddable chat widget
One-line install for a support chat bubble on any website, powered by public guest sessions and optional custom domains.
Web Push (VAPID) — browser notifications
FluxyChat ships a self-hosted Web Push implementation that is wire-compatible with Pusher Beams for browser notifications. You don't need to sign up for a t