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.

connection.js
javascript
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.

StateWhat it means
initializedThe client object was created but no connection attempt has started yet. No events are emitted in this state.
connectingAttempting to establish a WebSocket connection. Also the state during automatic reconnection attempts after a failure.
connectedLive, authenticated connection. Events flow freely. A socket_id is assigned.
unavailableConnection 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.
failedThe browser does not support WebSockets and no fallback transports are available. The SDK will not retry. Extremely rare in modern browsers.
disconnectedThe connection was closed intentionally via pusher.disconnect(). No automatic reconnection.

State Overview

initialized
Created, not yet connecting
connecting
Attempting WebSocket handshake
connected
Live, socket_id assigned
unavailable
Lost, retrying every 15s
failed
No WebSocket support, no retry
disconnected
Closed manually, no retry

Typical State Transitions

Normal startup:

initialized
connecting
connected

Brief network interruption (auto-recovers):

connected
connecting
connected

Extended internet loss (~30 seconds to detect):

connected
connecting
unavailable
retrying every 15s

Monitoring State Changes

Bind to individual states or to the generic state_change event to react to any transition.

monitor-state.js
javascript
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:

  1. Channel authorization: when subscribing to private or presence channels, your client sends its socket_id to your auth endpoint. Your server signs it with the channel name to prove the request is legitimate.
  2. Event exclusion: when you trigger an event from your server, you can pass the socket_id of 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.
socket-id-usage.js
javascript
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.

connection-errors.js
javascript
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});
CodeMeaning
4001App key not found. Double-check your App Key in the Soketify dashboard.
4003App is disabled. Usually means the app was blocked due to a billing issue or manual suspension.
4004Over connection limit. Your app has reached its maximum concurrent connection count. Upgrade your plan or check for connection leaks.
4005Path 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:

disconnect.js
javascript
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.

lib/pusher.ts
typescript
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});
OrderTracker.tsx
typescript
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