Connection
A single persistent WebSocket connection carries all of your channel subscriptions and events. Understanding the connection lifecycle (states, errors, and the socket ID) makes debugging much easier and helps you build resilient real-time features.
Establishing a Connection
A connection is created when you initialize the Pusher client. The SDK immediately starts the WebSocket handshake in the background, without needing to call connect manually.
1import Pusher from "pusher-js";
2
3const pusher = new Pusher("YOUR_APP_KEY", {
4 wsHost: "ws.soketify.com",
5 wsPort: 443,
6 wssPort: 443,
7 forceTLS: true,
8 disableStats: true,
9 enabledTransports: ["ws", "wss"],
10 cluster: "default",
11});
12
13// The connection starts immediately.
14// Subscribe to channels right away — the SDK queues them until connected.
15const channel = pusher.subscribe("my-channel");
Info
You can call pusher.subscribe() before the connection is established. The client queues subscriptions and processes them automatically once connected. You do not need to wait for the connected event before subscribing.
Connection States
The connection moves through a defined set of states. At any point you can read the current state from pusher.connection.state.
| State | What it means |
|---|---|
| initialized | The client object was created but no connection attempt has started yet. No events are emitted in this state. |
| connecting | Attempting to establish a WebSocket connection. Also the state during automatic reconnection attempts after a failure. |
| connected | Live, authenticated connection. Events flow freely. A socket_id is assigned. |
| unavailable | Connection could not be established or was lost, and the client is in a backoff-retry loop. Automatic retry every 15 seconds. Common when the user’s internet drops. |
| failed | The browser does not support WebSockets and no fallback transports are available. The SDK will not retry. Extremely rare in modern browsers. |
| disconnected | The connection was closed intentionally via pusher.disconnect(). No automatic reconnection. |
State Overview
Typical State Transitions
Normal startup:
Brief network interruption (auto-recovers):
Extended internet loss (~30 seconds to detect):
Monitoring State Changes
Bind to individual states or to the generic state_change event to react to any transition.
1// Bind to a specific state
2pusher.connection.bind("connected", () => {
3 console.log("Connected. Socket ID:", pusher.connection.socket_id);
4 showOnlineIndicator();
5});
6
7pusher.connection.bind("disconnected", () => {
8 console.log("Disconnected.");
9 showOfflineIndicator();
10});
11
12pusher.connection.bind("unavailable", () => {
13 console.log("Connection unavailable. Retrying...");
14 showReconnectingBanner();
15});
16
17// Or bind to all state transitions at once
18pusher.connection.bind("state_change", (states) => {
19 console.log(`${states.previous} → ${states.current}`);
20});
21
22// Read the current state at any time
23const currentState = pusher.connection.state; // "connected", "connecting", etc.
Show connection status in your UI
A small connection indicator (green/orange/red dot) that responds to state_change events dramatically improves the experience for users on flaky connections. They know the app is still alive and reconnecting rather than assuming it crashed.
The Socket ID
Once connected, every connection is assigned a unique socket_id. It serves two important purposes:
- Channel authorization: when subscribing to private or presence channels, your client sends its
socket_idto your auth endpoint. Your server signs it with the channel name to prove the request is legitimate. - Event exclusion: when you trigger an event from your server, you can pass the
socket_idof the originating client to exclude it from receiving that event. This prevents echo, since the client that caused an action does not need to hear its own update.
1// Read the socket_id once connected
2pusher.connection.bind("connected", () => {
3 const socketId = pusher.connection.socket_id;
4 console.log("My socket ID:", socketId); // e.g., "12345.67890"
5});
6
7// Common pattern: send socket_id with your API requests
8// so the server can exclude this client from the triggered event
9async function submitComment(text) {
10 await fetch("/api/comments", {
11 method: "POST",
12 headers: { "Content-Type": "application/json" },
13 body: JSON.stringify({
14 text,
15 socket_id: pusher.connection.socket_id, // exclude sender from event
16 }),
17 });
18}
19
20// Server: use the socket_id to exclude the sender
21app.post("/api/comments", async (req, res) => {
22 const { text, socket_id } = req.body;
23 await db.comments.create({ text });
24
25 await pusher.trigger("comments", "new-comment", { text }, {
26 socket_id, // this client won't receive the event
27 });
28
29 res.json({ ok: true });
30});
Connection Errors
Bind to the error event on the connection object to handle errors. The error payload includes a numeric code that tells you exactly what went wrong.
1pusher.connection.bind("error", (err) => {
2 const code = err?.error?.data?.code;
3 const message = err?.error?.data?.message;
4
5 switch (code) {
6 case 4001:
7 console.error("App key not found. Check your App Key.");
8 break;
9 case 4003:
10 console.error("App disabled. Check your Soketify dashboard.");
11 break;
12 case 4004:
13 console.error("Connection limit reached for this app.");
14 break;
15 case 4005:
16 console.error("Path not found. Check wsHost config.");
17 break;
18 default:
19 console.error("Connection error:", code, message);
20 }
21});
| Code | Meaning |
|---|---|
| 4001 | App key not found. Double-check your App Key in the Soketify dashboard. |
| 4003 | App is disabled. Usually means the app was blocked due to a billing issue or manual suspension. |
| 4004 | Over connection limit. Your app has reached its maximum concurrent connection count. Upgrade your plan or check for connection leaks. |
| 4005 | Path not found. Verify that wsHost is set to ws.soketify.com. |
Disconnecting
Browsers close WebSocket connections automatically when a user navigates away or closes the tab, so you rarely need to disconnect manually. If you do need to close the connection explicitly, for example when a user logs out, call:
1pusher.disconnect();
2// Connection state moves to "disconnected". No automatic reconnection.
3
4// To reconnect later:
5pusher.connect();
Info
Soketify (via Soketi) detects disconnected clients within a few seconds. Presence channel members are removed and webhooks fire after a short grace period (~3 seconds) to avoid spurious member-removed events from momentary connectivity blips.
Connection Management in React
Create the Pusher instance once (at the module level or inside a context provider) so it is shared across your entire app. Do not create a new instance per component; that wastes connections.
1// lib/pusher.ts — create once, import everywhere
2import Pusher from "pusher-js";
3
4export const pusher = new Pusher(process.env.NEXT_PUBLIC_SOKETIFY_KEY!, {
5 wsHost: "ws.soketify.com",
6 wsPort: 443,
7 wssPort: 443,
8 forceTLS: true,
9 disableStats: true,
10 enabledTransports: ["ws", "wss"],
11 cluster: "default",
12});
1// In a component: subscribe and unsubscribe cleanly
2import { useEffect } from "react";
3import { pusher } from "@/lib/pusher";
4
5export function OrderTracker({ orderId }: { orderId: string }) {
6 useEffect(() => {
7 const channelName = `private-order-${orderId}`;
8 const channel = pusher.subscribe(channelName);
9
10 channel.bind("status-update", (data: { status: string }) => {
11 setStatus(data.status);
12 });
13
14 // Unsubscribe when the component unmounts
15 return () => {
16 pusher.unsubscribe(channelName);
17 };
18 }, [orderId]);
19
20 // ...
21}
Next Steps
- Learn about channel types: public, private, encrypted, presence, and cache
- Understand the events system: binding, system events, naming rules, and limits
- Set up channel authentication: required for private and presence channels