Two complete, runnable builds that combine a channel type, an auth endpoint, and a server publisher into a real feature. Both use stock pusher-js and the stock Pusher server libraries — the only thing that changes is the host you point them at, so everything below drops straight into an existing Pusher codebase.

Live presence list

A “who’s online” roster on a presence-* channel: an auth endpoint that signs the subscription, plus a client that tracks members joining and leaving in real time.

Server-pushed notifications

Your backend triggers events; the browser binds and renders them, and keeps working across reconnects because channel state is restored on resubscribe.
Every snippet assumes you created an app in the Realtime Console and have its key, secret, and app ID to hand. The key is public (it ships in the browser bundle); the secret is server-only.

The one-line switch

All you change to run on TeleQuick is the host. Keep your key, your channel names, your event names, and your handlers.
import Pusher from "pusher-js";

// Point stock pusher-js at TeleQuick — nothing else changes.
const pusher = new Pusher("<APP_KEY>", {
  wsHost:   "realtime.telequick.dev",
  forceTLS: true,
  // Private/presence channels call this endpoint to sign the subscription.
  channelAuthorization: { endpoint: "/api/realtime/auth" },
});
import Pusher from "pusher";

// Same server library you already use — just add `host`.
export const realtime = new Pusher({
  appId:  process.env.REALTIME_APP_ID!,
  key:    process.env.REALTIME_APP_KEY!,
  secret: process.env.REALTIME_APP_SECRET!,
  host:   "realtime.telequick.dev",
  useTLS: true,
});
import os
from pusher import Pusher

realtime = Pusher(
    app_id=os.environ["REALTIME_APP_ID"],
    key=os.environ["REALTIME_APP_KEY"],
    secret=os.environ["REALTIME_APP_SECRET"],
    host="realtime.telequick.dev",
    ssl=True,
)
Want the QUIC transport instead of WebSocket? Swap pusher-js for the WebTransport/QUIC-capable fork — it speaks the identical Pusher protocol over an HTTP/3 connection (/app/<key>), so the recipes below are unchanged. See SDK & API for which client to install.

Recipe 1 — Live presence list

Presence channels (presence-*) are private channels that also carry a member roster. On subscribe you get the current members and every subsequent join/leave, so you can render “who’s online” with no polling. Because presence channels are authorized, you need a small auth endpoint. TeleQuick verifies its HMAC exactly like Pusher does, so you sign the subscription with the stock server library’s authorizeChannel.
1

Add the auth endpoint

When the browser subscribes to a private-* or presence-* channel, pusher-js POSTs the socket_id and channel_name to your channelAuthorization.endpoint. Return the signed payload. For presence, attach the member’s user_id and public user_info — this is what other clients see in the roster, so put only display-safe fields here.
import express from "express";
import { realtime } from "./realtime"; // the server client from above

const app = express();
app.use(express.urlencoded({ extended: false }));

app.post("/api/realtime/auth", (req, res) => {
  const { socket_id, channel_name } = req.body;

  // 🔒 Authenticate the *real* user from your own session/cookie first.
  const user = req.session?.user;
  if (!user) return res.status(403).send("not signed in");

  // Optionally scope who may join which room.
  if (channel_name.startsWith("presence-") && !user.canJoin(channel_name)) {
    return res.status(403).send("forbidden");
  }

  const authResponse = realtime.authorizeChannel(socket_id, channel_name, {
    user_id:   user.id,                         // required, unique per member
    user_info: { name: user.name, avatar: user.avatar },
  });
  res.send(authResponse);
});
from flask import Flask, request, session, jsonify, abort
from realtime_client import realtime  # the server client from above

app = Flask(__name__)

@app.post("/api/realtime/auth")
def realtime_auth():
    socket_id    = request.form["socket_id"]
    channel_name = request.form["channel_name"]

    user = session.get("user")
    if not user:
        abort(403)

    if channel_name.startswith("presence-") and not can_join(user, channel_name):
        abort(403)

    auth = realtime.authenticate(
        channel=channel_name,
        socket_id=socket_id,
        custom_data={
            "user_id": user["id"],
            "user_info": {"name": user["name"], "avatar": user["avatar"]},
        },
    )
    return jsonify(auth)
The auth endpoint is your trust boundary. Never take the user_id from the browser — derive it from your own authenticated session. Anyone who can call this endpoint can appear in the roster as whoever it returns.
2

Subscribe and render the roster

On the client, subscribe to a presence-* channel and read members. The pusher:subscription_succeeded event gives you the initial roster; pusher:member_added / pusher:member_removed keep it live.
import Pusher from "pusher-js";

const pusher = new Pusher("<APP_KEY>", {
  wsHost:   "realtime.telequick.dev",
  forceTLS: true,
  channelAuthorization: { endpoint: "/api/realtime/auth" },
});

const roster = new Map(); // user_id -> user_info
const channel = pusher.subscribe("presence-lobby");

// 1. Initial roster on join.
channel.bind("pusher:subscription_succeeded", (members) => {
  roster.clear();
  members.each((m) => roster.set(m.id, m.info));
  console.log("me:", members.me.id, "· online:", members.count);
  render();
});

// 2. Live joins and leaves.
channel.bind("pusher:member_added",   (m) => { roster.set(m.id, m.info); render(); });
channel.bind("pusher:member_removed", (m) => { roster.delete(m.id);     render(); });

// 3. Auth failures surface here (bad session, 403 from your endpoint).
channel.bind("pusher:subscription_error", (err) =>
  console.error("presence auth failed:", err.status));

function render() {
  const names = [...roster.values()].map((u) => u.name);
  document.querySelector("#online").textContent =
    `${names.length} online: ${names.join(", ")}`;
}
3

React to presence from your backend (optional)

Register the Presence webhook group in the console to receive member_added / member_removed server-side — handy for “mark user offline” logic without keeping a socket open. See console → webhooks for the delivery format and how to verify the X-Pusher-Signature HMAC.
A member’s presence is derived from their live connection. When a client’s last tab closes — or the network drops long enough — TeleQuick emits member_removed, so the roster self-heals without any client-side heartbeat. Fan-out spans shards and edge nodes automatically; you never run or name a message broker.

Recipe 2 — Server-pushed notifications

The classic pattern: your backend does work (an order ships, a job finishes) and you want it to appear instantly in the user’s browser. Publish from the server with trigger(); bind on the client. Use a private per-user channel (private-notifications-<userId>) so only the authorized user receives their notifications — reusing the same auth endpoint from Recipe 1.
1

Publish from the server

Anywhere in your backend, call trigger(channel, event, data). That single call fans the event out to every connected subscriber of that channel across the mesh.
import { realtime } from "./realtime";

export async function notify(userId: string, note: unknown) {
  await realtime.trigger(`private-notifications-${userId}`, "notification", note);
}

// e.g. from an order-shipped handler:
await notify("u_42", {
  id:    "n_8f1",
  kind:  "order_shipped",
  title: "Your order is on its way",
  at:    new Date().toISOString(),
});
from datetime import datetime, timezone
from realtime_client import realtime

def notify(user_id: str, note: dict) -> None:
    realtime.trigger(f"private-notifications-{user_id}", "notification", note)

notify("u_42", {
    "id": "n_8f1",
    "kind": "order_shipped",
    "title": "Your order is on its way",
    "at": datetime.now(timezone.utc).isoformat(),
})
# The control-plane REST publish API mirrors trigger() for scripts and
# services that don't run a Pusher server library.
curl -X POST "https://portal.telequick.dev/apps/$REALTIME_APP_ID/events" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "notification",
    "channels": ["private-notifications-u_42"],
    "data": "{\"title\":\"Your order is on its way\"}"
  }'
The REST body’s data is a string, exactly as in the Pusher HTTP API — JSON-encode your payload before putting it in the field. The server libraries do this for you. Authenticated publish (signing the request with your app secret) is handled by the server libraries; see console → publish a test event to try it from the browser first.
2

Bind on the client

Subscribe to the user’s private channel and bind the event. Rendering a toast, incrementing a badge, and playing a sound are all just handlers.
import Pusher from "pusher-js";

const pusher = new Pusher("<APP_KEY>", {
  wsHost:   "realtime.telequick.dev",
  forceTLS: true,
  channelAuthorization: { endpoint: "/api/realtime/auth" },
});

const channel = pusher.subscribe(`private-notifications-${CURRENT_USER_ID}`);

channel.bind("notification", (note) => {
  showToast(note.title);
  bumpUnreadBadge();
});
3

Survive reconnects

Connections drop — laptops sleep, phones switch from WiFi to cellular. You do not re-subscribe by hand: pusher-js replays your subscriptions and re-runs your auth endpoint automatically when the socket comes back, and TeleQuick carries the connection across a network change without a full reconnect where it can. Bind the connection lifecycle to drive UI, and backfill anything that may have been published while you were offline — the channel is live-only, so durable state comes from your own API.
// Reflect transport health in the UI.
pusher.connection.bind("state_change", ({ current }) => {
  setBanner(current === "connected" ? null : `reconnecting… (${current})`);
});

// On (re)connect, reconcile with the source of truth.
pusher.connection.bind("connected", async () => {
  const missed = await fetch("/api/notifications?since=" + lastSeenId).then((r) => r.json());
  for (const note of missed) applyNotification(note);
});

// Optional: react to a hard failure (e.g. app disabled, key revoked).
pusher.connection.bind("error", (err) => console.error("realtime error:", err));
Treat realtime as the fast path, not the system of record. A short GET …?since=<cursor> on reconnect closes the gap for the rare window where the socket was down while your server published — the pattern is the same one you’d use with hosted Pusher.
Notification fan-out uses the same trigger() → subscriber path as presence, spread across shards and edge nodes. You publish once; delivery to every online subscriber of the channel is handled for you.

Honest gaps

Shipped and used above: public / private-* / presence-* channels, HMAC subscription auth, client-* events, server trigger(), the REST publish API, and the channel-existence + presence webhook groups.Still rolling out: some webhook and batch-delivery features (for example batched event webhooks and larger multi-channel publishes) are in preview and may lag the hosted Pusher feature set. If a build depends on one, confirm it in the Realtime Console for your app before shipping.

Details

The channel model, transports (WebSocket and QUIC), and how fan-out works.

SDK & API

The client, server-publish, and auth surfaces — and which pusher-js to use.

Cookbook

Shorter copy-paste snippets: subscribe, private auth, client events, webhooks.

Console

Create apps and keys, publish test events, watch live stats, wire webhooks.