---
source_url: https://www.pubnub.com/docs/architecture/connection-management/monitor-and-respond-to-connection-status-changes
title: Monitor and respond to connection status changes
updated_at: 2026-09-30T07:20:08.000Z
---

# Monitor and respond to connection status changes

## Documentation index

To discover more PubNub resources:

1. Fetch [PubNub's llms.txt](https://www.pubnub.com/llms-full.txt) for a list of available pages in Markdown format.
2. Identify relevant URLs from that index.
3. Fetch the target pages.

Do not assume a path exists, always check the index first.

PubNub's SDK manages the subscribe loop and reconnection automatically. This page shows how to expose that behavior to your application so you can react to connection state changes.

Attach a status listener to your PubNub client so your application can tell a healthy idle connection from a lost one, and act when the SDK stops retrying. Attach it to the PubNub client object, not to an individual subscription or subscription set, and attach it once per client.

Before you start, make sure you have:

* **An initialized PubNub client** with your keyset's keys and a `userId`. Refer to [Quickstart](https://www.pubnub.com/docs/getting-started/quickstart.md).
* **At least one active subscription.** The statuses on this page describe the subscribe connection, so a client that never subscribes emits none of them.

Nothing needs enabling in the [Admin Portal](https://admin.pubnub.com/), and status events need no add-on.

If a listener is already attached and you only need to know what to do with each status, skip to [Branch on the status category](#branch-on-the-status-category).

## Add the listener, then subscribe

Add the listener to the client before you subscribe, because a listener receives only the events emitted after it's attached.

### JavaScript

```javascript
// add a status listener
pubnub.addListener({
  status: (s) => {
    console.log('Status', s.category);
  },
});
```

### Swift

```swift
// Sets a callback to handle connection state changes
pubnub.onConnectionStateChange = { newStatus in
  print("Connection status: \(newStatus)")
}
```

### Kotlin

```kotlin
pubnub.addListener(object : StatusListener {
    override fun status(pubnub: PubNub, status: PNStatus) {
        // Handle connection status updates
        println("Connection Status: ${status.category}")
    }
})
```

### Java

```java
pubNub.addListener((pubnub, status) -> {
    // Handle connection status updates
    System.out.println("Connection Status: " + status.getCategory());
});
```

### C#

```csharp
using PubnubApi;
using PubnubApi.EndPoint;

// Configuration
PNConfiguration pnConfiguration = new PNConfiguration(new UserId("myUniqueUserId"))
{
    SubscribeKey = "demo",
    PublishKey = "demo",
    Secure = true
};

// Initialize PubNub
Pubnub pubnub = new Pubnub(pnConfiguration);

SubscribeCallbackExt eventListener = new SubscribeCallbackExt(
    delegate(Pubnub pn, PNStatus e) { Console.WriteLine("Status event"); }
);

pubnub.AddListener(eventListener);
```

Most other SDKs document the same call in the **Add connection status listener** section of their Publish/Subscribe API reference, for example [Python](https://www.pubnub.com/docs/sdks/python/api-reference/publish-and-subscribe.md#add-connection-status-listener).

Keep the listener attached for the life of the client. Adding a second listener for the same purpose gives you duplicate status events to deduplicate.

## Branch on the status category

:::note Status category names vary by SDK
The five categories below are conceptual. Every SDK exposes them differently: different constant names, different fields on the event object, and not every SDK emits all five. Check your SDK's status events reference before writing this code, for example [JavaScript](https://www.pubnub.com/docs/sdks/javascript/status-events.md).
:::

Compare the category on each event against your SDK's own constants, for example `PNConnectedCategory` in JavaScript.

| Status | What to do |
| --- | --- |
| `Connected` | Clear any offline indicator in your UI. If the client had been disconnected before this event, run your catch-up path now. |
| `Subscription changed` | Update whatever your application tracks about the current subscription from the channel and channel-group arrays on the event. Don't treat it as a reconnect. |
| `Disconnected` | Treat it as expected, because it follows your own request to stop. [Resume the connection](#resume-the-connection) with your SDK's reconnect method rather than by calling subscribe again. |
| `Disconnected unexpectedly` | Show offline state and read the `error` field. The retries are already spent, so decide here whether to recover missed messages, and reconnect when it makes sense for the user. |
| `Connection error` | Read the `error` field before doing anything else. If it reports access denied, refresh the token. If it reports a network failure, reconnect when connectivity returns. |

In a browser, the JavaScript SDK also emits two network-level categories ahead of these five: `PNNetworkDownCategory` and `PNNetworkUpCategory`. See [Detect a browser network drop sooner](#detect-a-browser-network-drop-sooner).

Give `Connection error` and `Disconnected unexpectedly` separate handling even when they show the same offline UI, because repeating a request that was rejected reproduces the rejection. For why the two differ, refer to [Two failures that mean different things](https://www.pubnub.com/docs/architecture/connection-management/overview.md#two-failures-that-mean-different-things).

Don't start your own retry loop while you wait for a status. The JavaScript SDK's current subscription workflow handles intermediate connecting and reconnecting states internally, emitting no status event until a retry sequence succeeds or gives up. Treat that silence as the SDK's own [reconnection policy](https://www.pubnub.com/docs/architecture/connection-management/overview.md#reconnection-policies) still working.

## Resume the connection

Call the reconnect method when the client sits in `Disconnected` after your own stop request, and after you replace a rejected token. Subscribing again doesn't restart the subscribe loop.

### JavaScript

```javascript
pubnub.reconnect();
```

### Swift

```swift
// Reconnets to a stopped subscription with the previous subscribed channels and channel groups
pubnub.reconnect()
```

### Kotlin

```kotlin
pubNub.reconnect()
// or
val timetoken = 17276954606232118L // Example timetoken received in publish/signal response
pubNub.reconnect(timetoken)
```

### Java

```java
pubNub.reconnect();
// or
Long timetoken = 17276954606232118L; // Example timetoken received in publish/signal response
pubNub.reconnect(timetoken);
```

### C#

```csharp
using PubnubApi;

// Configuration
PNConfiguration pnConfiguration = new PNConfiguration(new UserId("myUniqueUserId"))
{
    SubscribeKey = "demo",
    PublishKey = "demo",
    Secure = true
};

// Initialize PubNub
Pubnub pubnub = new Pubnub(pnConfiguration);
        
pubnub.Reconnect<string>();
```

Reconnect in response to a user action or a status event rather than on a timer, because the SDK already applies its own [reconnection policy](https://www.pubnub.com/docs/architecture/connection-management/overview.md#reconnection-policies) to a failed request.

## Refresh a token when a status reports access denied

Follow this path only if your keyset uses [Access Manager](https://www.pubnub.com/docs/security/access-control/overview.md). A retry policy can't clear a rejected token, so this handling is yours.

1. **Read the error field** on the `Connection error` event. In JavaScript, an access failure arrives as `PNConnectionErrorCategory` with `PNAccessDeniedCategory` nested in that field.
2. **Request a new token** from your own server for the same `userId`, scoped with `read` permission on the channels and channel groups the client subscribes to.
3. **Set the new token** on the client:

### JavaScript

```javascript
pubnub.setToken('use-token-string-generated-by-grantToken()');
```

### Swift

```swift
import PubNubSDK

// Initializes a PubNub object with the configuration
let pubnub = PubNub(
  configuration: PubNubConfiguration(
    publishKey: "demo",
    subscribeKey: "demo",
    userId: "myUniqueUserId"
  )
)

// Update the authentication token granted by the server
pubnub.set(token: "#yourAuthToken")
```

### Kotlin

```kotlin
pubnub.setToken(
    "qEF2AkF0Gmgi5mVDdHRsGQU5Q3Jlc6VEY2hhbqFnc3BhY2UwMQhDZ3JwoENzcGOgQ3VzcqBEdXVpZKFmdXNlcjAxGCBDcGF0pURjaGFuoWdzcGFjZS4qAUNncnCgQ3NwY6BDdXNyoER1dWlkoWZ1c2VyLioYIERtZXRhoER1dWlkbmF1dGhvcml6ZWRVc2VyQ3NpZ1ggkOSK0vQY5LFE5IHctQ6rGokqHbRH8EopbQRGAbU7Zfo="
)
```

### Java

```java
pubnub.setToken("qEF2AkF0Gmgi5mVDdHRsGQU5Q3Jlc6VEY2hhbqFnc3BhY2UwMQhDZ3JwoENzcGOgQ3VzcqBEdXVpZKFmdXNlcjAxGCBDcGF0pURjaGFuoWdzcGFjZS4qAUNncnCgQ3NwY6BDdXNyoER1dWlkoWZ1c2VyLioYIERtZXRhoER1dWlkbmF1dGhvcml6ZWRVc2VyQ3NpZ1ggkOSK0vQY5LFE5IHctQ6rGokqHbRH8EopbQRGAbU7Zfo=");
```

### C#

```csharp
using PubnubApi;

//Create configuration
PNConfiguration pnConfiguration = new PNConfiguration(new UserId("myUniqueUserId"))
{
    SubscribeKey = "demo",
    PublishKey = "demo"
};
//Create a new PubNub instance
Pubnub pubnub = new Pubnub(pnConfiguration);
        
pubnub.SetAuthToken(
    "p0thisAkFl043rhDdHRsCkNyZXisRGNoYW6hanNlY3JldAFDZ3Jwsample3KgQ3NwY6BDcGF0pERjaGFuoENnctokenVzcqBDc3BjoERtZXRhoENzaWdYIGOAeTyWGJI");
```

Then [resume the connection](#resume-the-connection), because setting the token doesn't restart the subscribe loop on its own.

The secret key grants privileged access to your PubNub application. It must remain on a trusted server and must never be included in client applications.

For the grant call itself, refer to [How to update an expired token](https://www.pubnub.com/docs/security/access-control/update-expired-token.md).

## Detect a browser network drop sooner

Apply this step only to the JavaScript SDK running in a browser. Other environments have no equivalent signal and reach a terminal status only after the retries are spent.

* Leave `listenToBrowserNetworkEvents` at its default so the SDK also emits `PNNetworkDownCategory` and `PNNetworkUpCategory`. Use `PNNetworkDownCategory` to show offline state immediately instead of waiting for retries to finish.
* Leave `restore` at its default so the SDK keeps the current timetoken and the channel and channel-group list across the drop.
* If you set `restore: false`, track the subscribed channels and channel groups in your own cache and resubscribe when `PNNetworkUpCategory` arrives. The SDK resets both on network loss in that mode.

Confirm the current defaults for both options in the [JavaScript configuration reference](https://www.pubnub.com/docs/sdks/javascript/api-reference/configuration.md).

## Verify the listener

Test the failure paths before you rely on them.

1. Subscribe to a channel and confirm you receive `Connected`.
2. In your browser's developer tools, switch the network to **Offline**. In a browser, expect `PNNetworkDownCategory` right away. In other environments, wait for the retries to run out and expect `Disconnected unexpectedly`.
3. Switch the network back on. Expect `PNNetworkUpCategory` in a browser, then `Connected`.
4. To exercise the access-denied path, initialize a client with a token that has no `read` permission on the channel, then subscribe. Expect `Connection error` carrying an access-denied error.

Log the category and the `error` field in each case. A test that only checks that messages resume tells you nothing about which status your code took.

## Related tasks

* [How to receive messages effectively](https://www.pubnub.com/docs/design-patterns/receive-messages-effectively.md) to recover messages published while the client was disconnected.
* [How to stop receiving messages](https://www.pubnub.com/docs/pub-sub/subscribe/stop-receiving-messages.md) for the intentional disconnect that produces `Disconnected`.
* [How to update an expired token](https://www.pubnub.com/docs/security/access-control/update-expired-token.md) for the server-side half of a token refresh.
* [Connection management](https://www.pubnub.com/docs/architecture/connection-management/overview.md) for what the SDK retries on its own and what each status means.

Last updated at: 2026-09-30T07:20:08.000Z
