Invalid Signature issue

What I’m Trying to Achieve

  • Register/login users via my custom backend.
  • Create a Zoom meeting using the Zoom API.
  • Start or join the meeting using the Zoom Meeting SDK in the browser.

Current Setup

Frontend (Next.js with Zoom Meeting SDK 3.12.0)

  • I’m loading the Zoom Meeting SDK scripts dynamically and initializing it once loaded.
  • Users can register/login, create a meeting, and attempt to start/join it.
  • Relevant code snippet:
window.ZoomMtg.init({
  leaveUrl: "***localhost:3000***",
  isSupportAV: true,
  success: () => {
    window.ZoomMtg.join({
      signature: meetingData.signature, // Fetched from backend
      meetingNumber: meetingData.meetingNumber,
      userName: formData.name || "Zoom User",
      sdkKey: "UoCjUegUT16YHg1qRgb0Dg",
      userEmail: formData.email || "zoomtest@test.com",
      passWord: meetingData.password,
      success: () => console.log("Joined meeting successfully"),
      error: (err) => setError(err.message || "Failed to join meeting"),
    });
  },
  error: (err) => setError(err.message || "Failed to initialize Zoom"),
});

SDK initialization:

useEffect(() => {
  if (isSdkLoaded && window.ZoomMtg) {
    window.ZoomMtg.setZoomJSLib("***//source.zoom.us/3.12.0/lib***", "/av");
    window.ZoomMtg.preLoadWasm();
    window.ZoomMtg.prepareWebSDK();
  }
}, [isSdkLoaded]);

Backend (Laravel with Zoom API)

  • I’m using the Zoom Server-to-Server OAuth flow to generate an access token and create meetings.
  • I generate a signature for the Meeting SDK using JWT.
  • Relevant code (ZoomService.php): php:
public function createMeeting(array $data): array {
  $response = Http::withHeaders([
    'Authorization' => 'Bearer ' . $this->getAccessToken(),
    'Content-Type' => 'application/json',
  ])->post('***//api.zoom.us/v2/users/me/meetings***', [
    'topic' => $data['title'],
    'type' => 2,
    'start_time' => Carbon::parse($data['start_time'])->toIso8601String(),
    'duration' => $data['duration'],
    'timezone' => $data['timezone'],
  ]);
  return $response->json();
}

public function generateSignature(string $meetingNumber, int $role): string {
  $sdkKey = env('ZOOM_CLIENT_ID');
  $sdkSecret = env('ZOOM_CLIENT_SECRET');
  $iat = time() - 30;
  $exp = $iat + 60 * 60 * 2;
  $payload = [
    'sdkKey' => $sdkKey,
    'mn' => $meetingNumber,
    'role' => $role,
    'iat' => $iat,
    'exp' => $exp,
    'tokenExp' => $exp,
  ];
  return JWT::encode($payload, $sdkSecret, 'HS256');
}

The Issue

  • I can successfully register/login and create a meeting (meeting ID is returned).
  • However, when I attempt to start/join the meeting:
    • The ZoomMtg.init callback fires successfully, but ZoomMtg.join throws an error like “invalid signature” error code “3172”.
  • Logs show the signature and meeting data being fetched, but the meeting doesn’t launch in the browser.

What I’ve Tried

  • Verified SDK Key, Client ID, Client Secret, and access token generation.
  • Ensured the meeting ID and signature are correctly passed from backend to frontend.
  • Tested with different roles (host=1, participant=0).
  • Checked network requests—no obvious CORS or API errors.

Questions

  1. Is there an issue with how I’m initializing or using the Zoom Web API in Next.js?
  2. Could the signature generation on the backend be incorrect? Should ZOOM_CLIENT_ID be used as sdkKey, or do I need a separate SDK key?
  3. Are there any common pitfalls with Zoom Meeting SDK 3.12.0 that I might be missing?

Any help narrowing down whether this is a frontend, backend, or Zoom configuration issue would be greatly appreciated. I can provide more logs or details if needed. Thank you! Please note that all the url values here were modified removing https to be able to post

hi @mohamed4 ,
you can not run on a local url.
Use something like ngrok to give you an external public url while you develop.
all the best

John

Hi [John Drinkwater MVP].

Thank you so much for taking the time to reply—I really appreciate your suggestion and the help from an MVP like yourself!

I should clarify: my backend is deployed on a live server, but my frontend is still running locally on localhost:3000. I’ve set the leaveUrl to the local URL for development purposes, and I don’t believe this should affect the Zoom Meeting SDK’s ability to join a meeting, since the signature and meeting data are fetched from the live backend. However, I’m still encountering the “invalid signature” error (code 3172) when calling ZoomMtg.join.

Thanks again for your guidance

1 Like

hi @mohamed4

you CAN NOT use localhost.
authorisation is done between your app and Zoom.us and so any local url will never see the result and fail!

All the best

John

Hello @ John Drinkwater MVP
Again thank you for giving me some of your time to try to fix my issue
I’ve taken your advice and set my front end test sample project online but I’m still facing the same issue (invalid signature)
Here is the link to my project
zoom-test-rho.vercel.app/?code=XINJS2Cl0HUNPvzcOPgQo2N6pV2s9ojxg

  • Notice that I’ve generated this link from Zoom itself creating Authorization URL
  • Also I’ve removed adding https because I can’t add links on here

Again, Thank you very much

Hi @mohamed4 ,

This may be helpful to look at as well: