Realtime is wire-compatible with Pusher Channels. You keep the client and server libraries you already use — the same subscribe / bind / trigger surface, the same channel model, and your existing auth endpoint reused as-is — and just point them at TeleQuick. There is no bespoke API to learn. On the client you have two first-class, drop-in options — pick either without changing your channel or event code:
  • Stock pusher-js over WebSocket — repoint wsHost and you are done. Best when you want zero new dependencies and run on normal, reliable networks.
  • The TeleQuick QUIC-native realtime client — a pusher-js-API-compatible drop-in that prefers WebTransport/QUIC and falls back automatically. Best when mobile or lossy-network resilience matters (see When to pick which).
The server side is identical either way: the official Pusher server SDKs or the REST publish endpoint, same channels, same HMAC auth.
This page is a reference for the compatible surface. If you already run Pusher, the only required change is the connection host (client) and the host option (server). App credentials come from the Realtime Console.

Connect a client

Construct a client with your app key and point it at the TeleQuick realtime host. Choose the transport-matched client below — the channel, bind, and connection-state code after this step is identical regardless of which you pick.
Repoint stock pusher-js by setting wsHost. Nothing else changes — this is the pure drop-in migration path.
npm install pusher-js
yarn add pusher-js
Browser (pusher-js)
import Pusher from "pusher-js";

const pusher = new Pusher("<APP_KEY>", {
  wsHost: "realtime.telequick.dev",
  forceTLS: true,          // wss:// — TLS on the wire
  // Auth for private-* / presence-* channels (see "Authorize channels"):
  channelAuthorization: {
    endpoint: "/pusher/auth",   // your backend route
    transport: "ajax",
  },
});

const channel = pusher.subscribe("public-updates");
channel.bind("price.changed", (data) => {
  console.log("new price", data);
});
wsHost
string
required
The realtime host, realtime.telequick.dev. This is the one line that repoints stock pusher-js from Pusher’s cluster onto TeleQuick.
forceTLS
boolean
default:"false"
Set true to connect over wss://. Recommended for all production traffic.
wsPort / wssPort
number
Override only if you terminate on a non-default port. By default the client uses 443 under forceTLS.
pusher-js (and the compatible client) addresses your app by key, not by app ID — the app ID is only used server-side. The cluster option is ignored when you set wsHost explicitly, which is what you want here.
channelAuthorization.endpoint
string
Your backend route that signs private-* / presence-* subscriptions — identical for both clients. Only needed if you use private or presence channels.

Transport ladder

The Pusher protocol is what TeleQuick speaks; the QUIC-native client can carry it over three transports, negotiated in order and degrading automatically:
RungTransportHow it connectsWins when
1WebTransport / QUICHTTP/3 to /app/<key>Lossy, mobile, or high-latency links — no TCP head-of-line blocking, faster reconnects via 0-RTT resumption, survives network changes.
2WebSocketwss:// to /app/<key>Reliable wired networks, or where WebTransport is blocked. Identical to stock pusher-js.
3SockJSHTTP long-polling fallbackRestrictive proxies that break both of the above.
Browsers reach QUIC through WebTransport (HTTP/3). Native mobile apps can go further and use raw QUIC directly, avoiding the WebTransport framing layer — that is the native-SDK path.
Raw-QUIC on mobile is delivered through the native mobile SDKs, not the browser package. In the browser, “QUIC” always means WebTransport/HTTP-3. If you need the raw-QUIC mobile transport, confirm current availability for your platform in the Realtime Console before you depend on it.

When to pick which

Be honest with your own traffic: on a clean wired network, WebSocket-over-TCP is already fast and stock pusher-js is the simplest thing that works. The QUIC-native client earns its place on lossy, mobile, or high-latency networks, where TCP head-of-line blocking and slow reconnects hurt.
SituationRecommended client
Pure drop-in migration off Pusher, no new depsStock pusher-js (WebSocket)
Desktop / server on reliable wired networksStock pusher-js (WebSocket)
Mobile web, flaky Wi-Fi, cellular, roamingQUIC-native client
High packet loss or high RTT pathsQUIC-native client
Fast reconnect / connection migration mattersQUIC-native client
Both are drop-in and share the exact same channel, event, presence, and auth code — you can start on stock pusher-js and switch the import to @telequick/sdk/realtime later with no other changes if resilience needs grow.

Subscribe, bind, unsubscribe

// Public channel — open, no auth
const orders = pusher.subscribe("public-orders");

// Bind a handler for a single event…
orders.bind("order.created", (data) => render(data));

// …or catch every event on the channel
orders.bind_global((event, data) => console.log(event, data));

// Stop listening / leave
orders.unbind("order.created");
pusher.unsubscribe("public-orders");
pusher.subscribe(channelName)
Channel
Join a channel. Prefix decides the type: no prefix (or public-) is open, private- requires a signed subscription, presence- adds member state.
channel.bind(eventName, handler)
void
Register a handler for an event on that channel. bind_global receives all events.
pusher.unsubscribe(channelName)
void
Leave a channel; drops its binds and stops delivery.

Connection states

The client exposes the standard Pusher connection lifecycle. Bind to it to drive UI (a “reconnecting” banner, say) or to gate publishes:
pusher.connection.bind("state_change", ({ previous, current }) => {
  console.log(`${previous}${current}`);
});

pusher.connection.bind("connected", () => {/* ready */});
StateMeaning
initializedClient constructed, not yet connecting.
connectingOpening the transport / handshaking.
connectedLive; subscriptions and events flow.
unavailableReachability lost; the client retries with backoff.
failedTransport unsupported in this environment.
disconnectedClosed after pusher.disconnect().

Authorize private & presence channels

private-* and presence-* subscriptions are HMAC-signed with your app secret. The browser never holds the secret: pusher-js POSTs the socket_id and channel name to your channelAuthorization.endpoint, and your backend signs the response with a Pusher server library. This is the standard Pusher auth flow — reuse your existing endpoint unchanged.
1

Client requests authorization

On subscribe("private-…") or subscribe("presence-…"), pusher-js POSTs { socket_id, channel_name } to your auth endpoint.
2

Backend signs with the app secret

Your route constructs a Pusher server client with { appId, key, secret } and returns the signed auth payload. For presence channels you also attach the member’s user_id and user_info.
3

Client completes the subscribe

pusher-js forwards the signature to TeleQuick, which verifies the HMAC and admits the subscription.
import Pusher from "pusher";
import express from "express";

const pusher = new Pusher({
  appId: "<APP_ID>",
  key: "<APP_KEY>",
  secret: "<APP_SECRET>",          // never shipped to the browser
  host: "realtime.telequick.dev",
  useTLS: true,
});

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

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

  // Presence channel: identify the member
  if (channel_name.startsWith("presence-")) {
    const auth = pusher.authorizeChannel(socket_id, channel_name, {
      user_id: req.user.id,
      user_info: { name: req.user.name },
    });
    return res.send(auth);
  }

  // Private channel: signature only
  const auth = pusher.authorizeChannel(socket_id, channel_name);
  res.send(auth);
});
The app secret must live only on your server. Anyone with it can sign subscriptions and publish events. Mint and rotate it in the Realtime Console.
Presence channels deliver pusher:subscription_succeeded with the current member list, then pusher:member_added / pusher:member_removed as people come and go — the standard presence events, bound the usual way.

Publish from your server

Use the official Pusher server library, constructed with your { appId, key, secret, host }, and call trigger(channel, event, data). This fans the event out across TeleQuick’s edge — every subscriber on that channel receives it.
import Pusher from "pusher";

const pusher = new Pusher({
  appId: "<APP_ID>",
  key: "<APP_KEY>",
  secret: "<APP_SECRET>",
  host: "realtime.telequick.dev",
  useTLS: true,
});

await pusher.trigger("public-orders", "order.created", {
  id: "ord_123",
  total: 4200,
});

// Fan one event to several channels at once:
await pusher.trigger(
  ["public-orders", "private-ops"],
  "order.created",
  { id: "ord_123" },
);
trigger(channel, event, data)
Promise
Publish event with JSON data to one channel or an array of channels. data is delivered to every subscriber’s matching bind.
POST /apps/{app_id}/events
REST
The underlying publish endpoint on the realtime host. Body is { name, channel | channels, data }; requests are HMAC-signed with the app secret (the server libraries do this for you). The Console Event Creator shows the exact cURL for any event you compose.
data is transmitted as a JSON string. The Pusher server libraries serialize an object for you; over raw REST you pass a stringified body, as shown in the cURL tab.

Client events

On private-* and presence-* channels, subscribers can publish client-* events directly to each other (typing indicators, cursors) without a round trip through your server. Enable client events for the app in the Console settings first.
const channel = pusher.subscribe("presence-room-42");
channel.trigger("client-typing", { user: "ada" });
Client events must be prefixed client- and are only permitted on authenticated (private-* / presence-*) channels. They are never delivered back to the sender.

Webhooks

Register endpoints in the Console to receive signed event batches when channels become occupied or vacated and when presence members join or leave. Verify the X-Pusher-Signature HMAC (computed with the webhook signing secret) before trusting a payload.
EventFires when
channel_occupiedFirst subscriber joins a channel.
channel_vacatedLast subscriber leaves a channel.
member_addedA member joins a presence-* channel.
member_removedA member leaves a presence-* channel.
Channel-existence and presence webhooks plus per-delivery HMAC signing are shipped. Some batching and additional webhook event groups (for example client- event and cache webhooks) are still rolling out — treat those as preview and confirm availability for your deployment in the Console before you depend on them.

Compatibility scope

Public channels, private-* HMAC subscription auth, presence-* with member state, client-* events, subscribe/bind/unbind/unsubscribe, the full connection-state lifecycle, and server-side trigger (single and multi-channel) over both the server libraries and the REST publish endpoint. Both clients — stock pusher-js and the TeleQuick QUIC-native client (@telequick/sdk/realtime) — speak this surface identically, and the official Pusher server SDKs work by changing only the host.
Some webhook batching and the broader webhook event set are still landing (see above). The browser QUIC path rides WebTransport (HTTP/3) and always has the WebSocket/SockJS fallback beneath it; raw-QUIC on mobile ships through the native SDKs — confirm availability for your platform before depending on it. If you use an advanced or newer Pusher feature not listed under “drop-in”, verify it against your deployment first.

Next steps

Realtime — Details

How channels, transports, and the edge fan-out fit together.

Realtime Console

Mint app keys, publish test events, and wire webhooks.

Realtime — Cookbook

Copy-pasteable snippets: presence, private channels, server publish.

Realtime — Recipes

End-to-end builds: a live presence list and server-pushed notifications.