Troubleshooting

There are several possible integration issues that can lead to calls not being established. This section will cover the most frequent ones.

Connection issues

Connection issues usually happen when you provide an invalid token during the SDK setup. When this happens, a web socket connection can’t be established with our backend, resulting in errors when trying to connect to a call. Network issues and firewalls can also prevent connection from establishing. To handle these errors, make sure to handle rejections of the call.join() promise:

try {
  await call.join();
} catch (err) {
  setError(err); // handle error
}

Before call.join() rejects, a maximum of three attempts will be made to connect to a call with exponential backoff. This behavior can be customized or disabled:

try {
  await call.join({ maxJoinRetries: 1 }); // disable retries
} catch (err) {
  setError(err); // handle error
}

Connection problems can also occur during a call, for example when switching networks or if the signal is poor. In this case the SDK will try to reconnect automatically. See our Network Distruption guide for more info.

Expired tokens

When you initialize the StreamVideoClient object, you provide a token, as described here. The tokens generated in the docs have an expiry date, therefore, please make sure to always use a token with a valid expiry date. You can check the contents of a JWT token on websites like this one.

Additionally, when expiring tokens are used, you need to provide a tokenProvider when creating StreamVideoClient, that will be invoked when the existing token expires. This is your chance to update the token by generating a new one on your backend.

Wrong secret for token generation

When you start integrating the SDK into your app, you might copy-paste the token from the docs into your project. However, that will not work.

Tokens are generated with the help of the app secret (available in your dashboard), and are unique per app id. Your app id is different from the demo apps we have as examples in our docs.

On websites like this one, you can verify if the token is signed with the correct signature.

While developing, you can manually generate tokens by providing your secret and the user’s ID here. However, note that for production usage, your backend would need to generate these tokens.

User-token mismatch

The token can be valid and correct, but for the wrong user. Make sure that the token you provide matches the id of the user that is used when creating the StreamVideoClient object.

Debuggers

Our SDK will log errors or warnings in browser’s console unless disabled. We try to have a descriptive error messages that should guide you to faster problem resolution. Also, using browser’s Network tab can help you spot failing network requests and the reasons for it.

Ringing calls issues

Ringing calls issues usually present themselves in a failure to show the incoming call screen or failure to deliver a notification event to the user we’re trying to call.

Members of a call

One common issue is that you only specify one user and try to call the same user on another device. This will not work, if you are the caller, you will not receive a notification that you’re being called - you can’t call yourself.

As you would do it in the real world, you would need to specify another member (or members) that you want to call. Another important note - that member should also exist in Stream’s platform (it must have connected at least once). This is needed because we need to know the user’s device and where to send the call notification.

Reusing a call id

Call IDs in general can be reused - you can join a call with the same id many times. However, the ringing is done only once per call ID. Therefore, if you implement calls with ringing, make sure that you provide a unique ID every time, in order for the ring functionality to work. One option is to use a uuid as a call ID.

Unreliable or missing events

Missing or duplicated events are usually caused by improper subscription cleanup in React components.

Common causes:

  • Event listeners not cleaned up on unmount
  • Multiple subscriptions due to re-renders
  • Subscribing outside useEffect
  • Missing cleanup function

Proper event subscription pattern:

import { useEffect } from "react";
import { useCall } from "@stream-io/video-react-sdk";

export const MyComponent = () => {
  const call = useCall();
  useEffect(() => {
    if (!call) return;
    const unsubscribe = call.on("call.session_started", (event) => {
      console.log("Call started:", event);
    });
    return () => unsubscribe();
  }, [call]);
  // ...
};

Always subscribe inside useEffect, return a cleanup function, and include correct dependencies.

Stale call instances

Call instances must be properly cleaned up to avoid state leakage between calls.

import { useEffect, useState } from "react";
import { Call } from "@stream-io/video-react-sdk";

export const MyCallComponent = ({ callType, callId, client }) => {
  const [call, setCall] = useState<Call>();
  useEffect(() => {
    const newCall = client.call(callType, callId);
    newCall.join().then(
      () => setCall(newCall),
      (err) => console.error("Failed to join:", err),
    );
    return () => {
      newCall.leave().catch((err) => console.error("Failed to leave:", err));
      setCall(undefined);
    };
  }, [callType, callId, client]);
  // ...
};

Always call call.leave() in cleanup and use unique call IDs for ringing calls.

Call timeouts or unexpected cancellations

Calls may auto-cancel due to timeout settings:

SettingPurposeDefault
ring.auto_cancel_timeout_msTimeout for unanswered ringing calls30-60 seconds
session.inactivity_timeout_secondsTimeout for inactive call sessionsVaries

If calls time out despite users joining:

  • Verify the callee called call.join() (accepting the call)
  • Check that call.state.callingState transitions to CallingState.JOINED
  • Review server-side logs with the call ID for state transitions

Event subscription best practices

Subscribe to events inside useEffect with proper cleanup:

useEffect(() => {
  if (!call) return;
  const unsubscribers = [
    call.on("call.accepted", handleAccepted),
    call.on("call.rejected", handleRejected),
  ];
  return () => unsubscribers.forEach((unsub) => unsub());
}, [call]);

Avoid subscribing in render or event handlers without cleanup.

Device permissions

Camera/microphone permission denied

If users previously denied permissions, browsers will not prompt again. Users must manually reset permissions in browser settings.

For Chrome: Settings → Privacy and security → Site Settings → Camera/Microphone → Allow

Permission prompts not appearing

Error: Permission was not granted previously, and prompting again is not allowed

Cause: Permission was previously denied. Instruct users to manually allow permissions in browser settings.

Screen sharing issues

Screen share button not appearing

Prerequisites:

  1. User must have the screenshare capability
  2. Screen sharing must be enabled in Dashboard for the call type

Check: Dashboard → Call Types → [Your Call Type] → Permissions → verify screenshare is enabled

Multiple screen shares per user

Web browsers (getDisplayMedia API) allow only one screen share per participant. Multiple participants can screen share concurrently in the same call.

Logging

For easier debugging, you can turn on more verbose logging. To do that, follow these instructions.