Description
I created the Server-to-Server app, and I am trying to use it with React/ZoomMtgEmbedded/@zoom/meetingsdk.
Expectation: For users to join a zoom call using my app, without having to register/log in into Zoom.
Browser Console Error
{type: ‘JOIN_MEETING_FAILED’, reason: ‘Signature is invalid.’, errorCode: 3712}
errorCode: 3712
reason: “Signature is invalid.”
type: “JOIN_MEETING_FAILED”
[[Prototype]]: Object
To Reproduce(If applicable)
Here is what I did so far:
-
Created the app on marketplace.zoom.us
Develop > Build Server-to-Server App -
Filled in basic info (app name, description …)
-
In “Scopes”, picked :
/meeting:read:admin
/meeting:write:admin
/whiteboard:read:admin
/whiteboard:write:admin -
Activated the account (got green checkmark and the message “Your app is activated on the account
Your app is allowed to invoke your selected Zoom APIs”).
So, at this point I have ACCOUNT_ID, CLIENT_ID and CLIENT_SECRET from “App Credentials”, and activated app.
-
Using Postman made a POST request to " /oauth/token" by sending params:
grant_type: account_credentials
account_id: ACCOUNT_ID
and got the “access_token” in response (as explained in this video tutorial on the Zoom YT channel:
watch?v=OkBE7CHVzho -
Used “access_token” to create the meeting by making POST request to “/v2/users/me/meetings”, where “access_token” is used for authorization (Bearer) as seen in the video mentioned above.
At this point, when I logged into my account, I could see that the meeting was scheduled.
- Used meetingsdk-auth-endpoint-sample from zoom’s github
created the node server for signature creation. Added my CLIENT_ID and CLIENT_SECRET
Server running on localhost:4000
const express = require("express");
const bodyParser = require("body-parser");
const cors = require("cors");
const KJUR = require("jsrsasign");
const app = express();
const port = process.env.PORT || 4000;
app.use(bodyParser.json());
app.use(cors());
app.options("*", cors());
const CLIENT_ID = [REDACTED];
const CLIENT_SECRET = [REDACTED];
app.post("/", (req, res) => {
const iat = Math.round(new Date().getTime() / 1000) - 30;
const exp = iat + 60 * 60 * 2;
const oHeader = { alg: "HS256", typ: "JWT" };
const role = req.body.role;
const meetingNumber = req.body.meetingNumber;
const oPayload = {
sdkKey: CLIENT_ID,
mn: req.body.meetingNumber,
role: req.body.role,
iat: iat,
exp: exp,
appKey: CLIENT_ID,
tokenExp: iat + 60 * 60 * 2,
};
const sHeader = JSON.stringify(oHeader);
const sPayload = JSON.stringify(oPayload);
const signature = KJUR.jws.JWS.sign(
"HS256",
sHeader,
sPayload,
CLIENT_SECRET
);
res.json({
signature: signature,
});
});
app.listen(port, () =>
console.log(
`Zoom Meeting SDK Auth Endpoint Sample Node.js listening on port ${port}!`
)
);
- Created React app using vite.
Installed @zoom/meetingsdk (version: “^3.1.6”)
Used the example and documentation from meetingsdk-web from zoom’s github
Here is the created component:
import React from "react";
import "./App.css";
import ZoomMtgEmbedded from "@zoom/meetingsdk/embedded";
const client = ZoomMtgEmbedded.createClient();
let meetingSDKElement = document.getElementById("meetingSDKElement"); // this element added in index.html
const App = () => {
let authEndpoint = "http://localhost:4000"; // my server
let sdkKey = [REDACTED]; // CLIENT_ID as instructed in the documentation
let meetingNumber = "84806506821"; // Meeting number obtained from the response when creating the meeting (STEP 6)
let passWord = [REDACTED]; // Password obtained in response (STEP 6)
let role = 0; // Participant role (0 for attendee) - I am trying to join as an attendee
let userName = "UserNameTest";
client
.init({
zoomAppRoot: meetingSDKElement,
language: "en-US",
patchJsMedia: true,
})
.then(async () => {
const signature = await getSignatureValue();
console.log(signature); // signature is received and printed
if (!signature) {
throw new Error("Failed to get signature");
}
client
.join({
sdkKey: sdkKey, // CLIENT_ID
signature: signature, // Signature obtained from the server (localhost:4000)
meetingNumber: meetingNumber,
password: passWord,
userName: userName,
})
.then(() => {
console.log("joined successfully");
})
.catch((error) => {
console.log(error);
});
})
.catch((error) => {
console.log(error);
});
// passing meetingNumber and role to obtain signature from my server
async function getSignatureValue() {
try {
const response = await fetch(authEndpoint, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
meetingNumber: meetingNumber,
role: role,
}),
});
if (!response.ok) {
throw new Error("Failed to get signature");
}
const responseData = await response.json();
return responseData.signature;
} catch (error) {
console.error(error);
return null;
}
}
return (
<div className="App">
<main>
<h1>Zoom meeting test</h1>
</main>
</div>
);
};
export default App;
At this point, when I run the app, and try to join a meeting, I can see the zoom video element with a spinner, and after a few seconds a pop-up that says:
“Fail to join meeting. Signature is invalid.”
**Device **
Tried to run on several browsers and 2 different machines (MacOS and Linux machines)
I hope I provided everything that is needed, and I hope someone will be able to help me out. I am not sure if I am doing things the correct way, but I tried to follow the documentation the best I can.
I will stand-by to fill in any additional info that is needed.
Thanks you in advance.