Generic 'Failed to join the meeting' error response from react component view

Hello everyone, I’ve been struggling with using the Web SDK for the component view in React JS. I have checked out the samples and have read the documentation but keep encountering an error like this:

error log: {type: ‘JOIN_MEETING_FAILED’, reason: ‘Fail to join the meeting.’, errorCode: 200}

I want to be able to start and join the meeting that I created as a host through my React application. My issue with this is that the error is very generic. I can’t understand what’s going on with this? To break down whats happening:

  1. The meeting is successfully being created after getting an access token (I can join the meeting in zoom natively on the windows application by using the url from the response object)

  2. I have checked my client secret key against jwt.io and am receiving ‘signature verified’

  3. I am including the users ZAK key that I receive through the endpoint: ‘https://api.zoom.us/v2/users/me/token?type=zak

I have verified that all values that can be seen in the code below are being passed correctly. I am using Chrome as my browser Version 119.0.6045.124 (Official Build) (64-bit)
but have also tried Edge and am encountering the same issue:
Version 119.0.2151.58 (Official build) (64-bit)

Which Web Meeting SDK version?
2.18

Meeting SDK Code Snippets

import React, { useEffect, useState } from 'react';
import axios from 'axios';
import { Template } from './template';
import { useSearchParams } from 'react-router-dom';
import ZoomMtgEmbedded from '@zoomus/websdk/embedded';
import { Button } from 'react-bootstrap';

export const ZoomMeeting = () => {
  const [searchParams] = useSearchParams();
  const code = searchParams.get('code');
  const [meetingNumber, setMeetingNumber] = useState(null);
  const [meetingPassword, setMeetingPassword] = useState('');
  const [zak, setZak] = useState('');
  const client_id = '87esZo1CRee1aSSfMssyFA';

  const requestAccessToken = async () => {
    try {
      const zakRequest = await axios.post(
        `https://localhost:8081/api/v2/zoom/access`,
        { code: code, url: 'https://localhost:3000/zoom/meeting' },
        { headers: { 'Content-Type': 'application/json' } }
      );
      setZak(zakRequest.data.zak);
      setMeetingNumber(zakRequest.data.mn);
      setMeetingPassword(zakRequest.data.password);

      console.log(zakRequest.data);
    } catch (err) {
      console.log(err);
    }
  };

  const startMeeting = async () => {
    try {
      const client = ZoomMtgEmbedded.createClient();
      await requestAccessToken();
      console.log('After state update:', zak, meetingNumber, meetingPassword);
      const username = localStorage.getItem('user');
      let meetingSDKElement = document.getElementById('meetingSDKElement');
      console.log('Meeting Number: ' + meetingNumber);
      const sig = await axios.post('https://localhost:8081/api/v2/zoom/CreateMeetingToken', {
        mn: meetingNumber,
      });
      console.log({
      sdkKey: client_id,
      signature: sig.data,
      meetingNumber: meetingNumber,
      password: meetingPassword,
      userName: username,
      zak: zak})
      await client.init({ zoomAppRoot: meetingSDKElement, language: 'en-US' });
      await client.join({
        sdkKey: client_id,
        signature: sig.data,
        meetingNumber: meetingNumber,
        password: meetingPassword,
        userName: username,
        zak: zak,
      });
      console.log(client.getCurrentMeetingInfo())
    } catch (error) {
      ZoomMtgEmbedded.destroyClient();
      console.error('Error starting the meeting:', error);
    }
  };

  useEffect(() => {
    // Fetch meeting details when the component mounts
    if (code) {
      requestAccessToken();
    }
  }, [code]);

  useEffect(() => {
    // This will log the updated state values after they are set
    console.log('State values:', zak, meetingNumber, meetingPassword);
  }, [zak, meetingNumber, meetingPassword]);

  return (
    <Template>
      <div id='meetingSDKElement'>ZoomRoom</div>
      <Button onClick={startMeeting}>Start the meeting</Button>
    </Template>
  );
};

Server Code Snippets

  1. Generate Meeting Signature
ZoomRouter.post('/CreateMeetingToken', async (request, response) => {
    try {
        const iat = Math.round(new Date().getTime() / 1000);
        const exp = iat + 60 * 60 * 2;

        const oHeader = { alg: 'HS256', typ: 'JWT' };
        const sHeader = JSON.stringify(oHeader);

        const oPayload = {
            appKey: process.env.ZOOM_SDK_CLIENT_ID,
            sdkKey: process.env.ZOOM_SDK_CLIENT_ID,
            meetingNumber: request.body.mn,
            role: 1,
            iat: iat,
            exp: exp,
            tokenExp: exp,
        };

        const sPayload = JSON.stringify(oPayload);

        // Sign the JWT
        const sdkJWT = KJUR.jws.JWS.sign('HS256', sHeader, sPayload, process.env.ZOOM_SDK_CLIENT_SECRET);

        return response.status(200).json(sdkJWT);
    } catch (err) {
        console.log(err);
        return response.status(500).json({ error: 'Internal server error' });
    }
});
  1. Get ZAK and Meeting Number + Password

ZoomRouter.post('/access', async (request, response) => {
    if (!request.body.code || !request.body.url) {
        return response.status(400).json({ error: 'Invalid request body' });
    }

    try {
        const client_id = process.env.ZOOM_SDK_CLIENT_ID;
        const client_secret = process.env.ZOOM_SDK_CLIENT_SECRET;

        const data = {
            code: request.body.code,
            grant_type: 'authorization_code',
            redirect_uri: request.body.url,
        };

        const formData = querystring.stringify(data);

        const authHeader = Buffer.from(`${client_id}:${client_secret}`).toString('base64');

        const config = {
            headers: {
                Authorization: `Basic ${authHeader}`,
                'Content-Type': 'application/x-www-form-urlencoded',
            },
        };

        const accessToken = (await axios.post('https://zoom.us/oauth/token', formData, config)).data.access_token;
        console.log(`Bearer ${accessToken}`)
        const zakToken = await axios.get('https://api.zoom.us/v2/users/me/token?type=zak',{headers:{Authorization:'Bearer ' + accessToken}})
        let working = false
        let tryCounter = 0
        let meeting = null
        const pause = (milliseconds:number) => {
            return new Promise(resolve => setTimeout(resolve, milliseconds));
          };
        await pause(5000);
        while(!working && tryCounter <= 2){
            try{

                meeting = await axios.post('https://api.zoom.us/v2/users/me/meetings',{},{
                    headers: {
                        Authorization: `Bearer ${accessToken}`
                    },
                })
                working = true
                console.log('success')
            }catch{
                tryCounter += 1
                console.log('catch')
                await pause(10000);
            }
        }
        console.log(meeting?.data)
        return response.status(200).json({zak:zakToken.data.token,mn:meeting?.data.id,password:meeting?.data.encrypted_password,email:meeting?.data.host_email});
    } catch (err) {
        console.log(err)
        return response.status(500).json({ error: 'Internal server error' });
    }
});

@angelojoudah ,

Thank you for reporting this! Based on the code you shared, everything appears to be in order. Granted, I haven’t inspected the actual values, but your implementation seems like it should work as expected. Can you clarify if this issue only occurs when you try to join as a host? What happens if you join as a participant? Also, have you been able to successfully join a meeting at all?

Hi, thanks for the response.

Q. Can you clarify if this issue only occurs when you try to join as a host? What happens if you join as a participant? Also, have you been able to successfully join a meeting at all?

A. When my backend server creates a meeting, the returned object looks like this:

{
  uuid: 'zPTnbrzVRG6NszDLoWpO1Q==',
  id: 71306658375,
  host_id: 'NUrGtSxCQFqkSEWVxNs6jw',
  host_email: 'angelojoudah@gmail.com',
  topic: 'Zoom Meeting',
  type: 2,
  status: 'waiting',
  start_time: '2023-11-16T13:17:18Z',
  duration: 60,
  timezone: 'America/New_York',
  created_at: '2023-11-16T13:17:18Z',
  start_url: 'https://us04web.zoom.us/s/71306658375?zak=eyJ0eXAiOiJKV1QiLCJzdiI6IjAwMDAwMSIsInptX3NrbSI6InptX28ybSIsImFsZyI6IkhTMjU2In0.eyJhdWQiOiJjbGllbnRzbSIsInVpZCI6Ik5Vckd0U3hDUUZxa1NFV1Z4TnM2anciLCJpc3MiOiJ3ZWIiLCJzayI6IjAiLCJzdHkiOjEwMCwid2NkIjoidXMwNCIsImNsdCI6MCwibW51bSI6IjcxMzA2NjU4Mzc1IiwiZXhwIjoxNzAwMTQ3ODM4LCJpYXQiOjE3MDAxNDA2MzgsImFpZCI6Inl3UUlXSEhIUUJ5MzV1NGtBc3BkbVEiLCJjaWQiOiIifQ.FJi4V5mQgiGsNNeNmJp2XGui7H5fuhxsZqiTpT-EI4Q',
  join_url: 'https://us04web.zoom.us/j/71306658375?pwd=X4ahZ2b6h7alzTGCrmK3PaEV5ggBe8.1',
  password: 'Xt1tM0',
  h323_password: '058682',
  pstn_password: '058682',
  encrypted_password: 'X4ahZ2b6h7alzTGCrmK3PaEV5ggBe8.1',
  settings: {
    host_video: false,
    participant_video: false,
    cn_meeting: false,
    in_meeting: false,
    join_before_host: false,
    jbh_time: 0,
    mute_upon_entry: false,
    watermark: false,
    use_pmi: false,
    approval_type: 2,
    audio: 'voip',
    auto_recording: 'none',
    enforce_login: false,
    enforce_login_domains: '',
    alternative_hosts: '',
    alternative_host_update_polls: false,
    close_registration: false,
    show_share_button: false,
    allow_multiple_devices: false,
    registrants_confirmation_email: true,
    waiting_room: true,
    request_permission_to_unmute_participants: false,
    registrants_email_notification: true,
    meeting_authentication: false,
    encryption_type: 'enhanced_encryption',
    approved_or_denied_countries_or_regions: { enable: false },
    breakout_room: { enable: false },
    internal_meeting: false,
    continuous_meeting_chat: { enable: false, auto_add_invited_external_users: false },
    participant_focused_meeting: false,
    push_change_to_calendar: false,
    resources: [],
    alternative_hosts_email_notification: true,
    show_join_info: false,
    device_testing: false,
    focus_mode: false,
    enable_dedicated_group_chat: false,
    private_meeting: false,
    email_notification: true,
    host_save_video_order: false,
    sign_language_interpretation: { enable: false },
    email_in_attendee_report: false
  },
  pre_schedule: false
}

based on this, I assume because of the ‘join_before_host: false’ value that I wouldn’t be able to join the meeting unless the host has started the meeting? I will try as a different user. But I have been able to start the meetings if I use the start_url value on the zoom application for windows

Also: I have tried both the encrypted password as well as the password fields from the object, neither seem to work either way, but I am confused as to which I should be using?

Final: I am using node 16.5.1. Is this sufficient for the Web SDK version I am using?

That’s correct! However, for testing, you can change or update that property to True.

How are you attempting to use the ‘start_url’ value in the Zoom application for Windows? Can you describe the exact steps you’re taking? Additionally, can you clarify if you are seeking to understand the workflow of initiating a meeting with the SDK as a Host ?

The password field should work

Yes, that is sufficient.

Yes, on the react snippet of code on my first page, I want this page to serve for starting the meeting, atleast for now. I might edit it to allow the user later on to change whether they are starting the meeting or joining as a participant by changing the value of the ‘Role’ field when signing the JWT.

But for now, all I want is for the component view to successfully load a meeting. Whenever I use the values returned to start the call, I am simply met with the error : {type: ‘JOIN_MEETING_FAILED’, reason: ‘Fail to join the meeting.’, errorCode: 200}

As for starting the meeting through the url, what I meant by this was verifying the meeting was actually created and working by taking the url provided in the ‘start_url’ field and dropping it in my browser. This would then prompt chrome to ask if I wanted to launch the zoom application I have installed on my pc and run the meeting through there.

But, this is not my desired goal. What I want is for my react application to run the meeting in component view. I only mention the above paragraph as a means of checking whether the meeting created was valid and will start under other circumstances.

As always, thanks in advance for the support!

Edit: I am wondering if SSL issues could somehow play a role into this? My website is being developed locally and as such, is using a self-signed certificate that has to be implicitly trusted. The architecture so far is running on Minikube, a locally hosted kubernetes cluster. The website runs just fine otherwise, it is just this feature that is giving me trouble. I have not yet tested on an actual production server with an official SSL Certificate

This topic was automatically closed 30 days after the last reply. New replies are no longer allowed.