Signature Invalid occur often

I am getting a signature and also can able to join the meeting after the end of one meeting.
creating another meeting that time I am getting the signature invalid issue.
kindly some buddy help me.

below i have mentioned the signature generate function.

1] How we can identify whether the particular signature is valid for certain time ? OR Expiry logic ?
2] Is there any time conversion / Offset is required to generate the signature ?
3] How signature gets validated on zoom server, I mean what kind of parameter consider while checking the signature in order to run the zoom meeeting ?

private static long ToEpoch(DateTime value) => (value.Ticks - 621355968000000000) / (10000 * 1000);

    public static string GenerateZoomSdkToken(string sdkKey, string sdkSecret, string meetingNumber,string role)
    {
     
        var now = DateTime.UtcNow;

        int roleId = Convert.ToInt32(role);
        Int64 MeetingNumber = Convert.ToInt64(meetingNumber);

        var iat = ToEpoch(now);
       // var exp = ToEpoch(now.AddMinutes(2));
        var exp = ToEpoch(now.AddMinutes(10));

        // Create Security key  using private key above:
        var securityKey = new Microsoft.IdentityModel.Tokens.SymmetricSecurityKey(Encoding.UTF8.GetBytes(sdkSecret));

        // length should be >256b
        var credentials = new SigningCredentials(securityKey, SecurityAlgorithms.HmacSha256);

        //Finally create a Token
        var header = new JwtHeader(credentials);

        var zoomPayload = new JwtPayload{
            {"sdkKey", sdkKey},
            {"mn", MeetingNumber},
            {"role", roleId},
            {"iat", iat},
            {"exp", exp},
            {"appKey",sdkKey},
            {"tokenExp", exp}
        };

        var secToken = new JwtSecurityToken(header, zoomPayload);
        var handler = new JwtSecurityTokenHandler();

        // Token to String so you can use it in your client
        var tokenString = handler.WriteToken(secToken);

        return tokenString;
    }

Hi @nirmaltiwari139 ,

I want to make sure I’m understanding what’s going on correctly. Your signature is working to join one meeting, but when you create a new meeting, it’s no longer valid. Is this correct? If so, how close together, time-wise, are the meetings being created? I ask this to see if it may have anything to do with the token expiring, which I think you were also getting at.

Thanks,
Rehema

did you get this fixed?

@rehema.zoom i am having same issue below is my code

package edu4all.service;

import java.sql.Date;
import java.util.Base64;

import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;

import com.nimbusds.jose.*;
import com.nimbusds.jose.crypto.MACSigner;
import com.nimbusds.jwt.JWTClaimsSet;
import com.nimbusds.jwt.SignedJWT;

@Service
public class ZoomAuthService {

@Value("${zoom.client.id}")
private String clientId;  // Your Zoom OAuth Client ID

@Value("${zoom.client.secret}")
private String clientSecret;  // Your Zoom OAuth Client Secret

@Value("${zoom.redirect.uri}")
private String redirectUri;  // Your Zoom OAuth Redirect URI

@Value("${zoom.access.token}")
private String storedAccessToken; // The stored access token from properties file

@Value("${zoom.refresh.token}")
private String storedRefreshToken; // The stored refresh token from properties file

private static final String ZOOM_OAUTH_TOKEN_URL = "https://zoom.us/oauth/token";
private static final String ZOOM_CREATE_MEETING_URL = "https://api.zoom.us/v2/users/me/meetings";

// Step 1: Create Zoom Meeting using JWT
public String createZoomMeeting() {
    String accessToken = storedAccessToken; // Use the stored access token

    // If the access token is expired, refresh it
    if (isAccessTokenExpired()) {
        accessToken = refreshAccessToken(storedRefreshToken);
    }

    String meetingUrl = ZOOM_CREATE_MEETING_URL;

    // Create the body for the meeting creation request
    String requestBody = "{\n" +
            "\"topic\": \"LAKSHAN\",\n" +
            "\"type\": 2,\n" +
            "\"start_time\": \"2025-05-15T15:00:00Z\",\n" +
            "\"duration\": 30,\n" +
            "\"timezone\": \"UTC\",\n" +
            "\"agenda\": \"Session Agenda\"\n" +
            "}";

    // Prepare headers with the access token
    HttpHeaders headers = new HttpHeaders();
    headers.set("Authorization", "Bearer " + accessToken);
    headers.set("Content-Type", "application/json");

    // Make the request to create the meeting
    RestTemplate restTemplate = new RestTemplate();
    ResponseEntity<String> response = restTemplate.exchange(meetingUrl, HttpMethod.POST, new HttpEntity<>(requestBody, headers), String.class);

    // Log the entire response
    System.out.println("Zoom Meeting Creation Response: " + response.getBody());

    // Extract the meeting number from the response (meeting ID)
    String meetingNumber = extractMeetingId(response.getBody());

    // Log the meeting number
    System.out.println("Meeting Number (ID): " + meetingNumber);

    return response.getBody();
}

// Helper method to extract the meeting ID (meeting number) from the response
private String extractMeetingId(String responseBody) {
    try {
        String meetingId = responseBody.split("\"id\":")[1].split(",")[0];
        return meetingId;
    } catch (Exception e) {
        e.printStackTrace();
        return null;  // Return null if extraction fails
    }
}

// Helper method to check if the access token is expired
private boolean isAccessTokenExpired() {
    // You can implement token expiration check here (e.g., compare the expiration timestamp with current time)
    return false;
}

// Step 2: Refresh the access token using the stored refresh token
private String refreshAccessToken(String refreshToken) {
    String tokenUrl = ZOOM_OAUTH_TOKEN_URL;

    // Construct the Basic Authorization header
    String authHeader = "Basic " + Base64.getEncoder().encodeToString((clientId + ":" + clientSecret).getBytes());

    // Prepare the body for refreshing the access token
    String body = "grant_type=refresh_token&refresh_token=" + refreshToken;

    // Prepare the HTTP request headers
    HttpHeaders headers = new HttpHeaders();
    headers.set("Authorization", authHeader);
    headers.set("Content-Type", "application/x-www-form-urlencoded");

    // Send the request to Zoom's OAuth endpoint
    RestTemplate restTemplate = new RestTemplate();
    HttpEntity<String> entity = new HttpEntity<>(body, headers);
    ResponseEntity<String> response = restTemplate.exchange(tokenUrl, HttpMethod.POST, entity, String.class);

    // Log and return the response from Zoom (contains the new access token)
    System.out.println("Zoom OAuth Token Refresh Response: " + response.getBody());

    // Extract and return the new access token from the response
    return extractAccessToken(response.getBody());
}

// Helper method to extract the access token from the response
private String extractAccessToken(String responseBody) {
    String accessToken = responseBody.split("\"access_token\":\"")[1].split("\"")[0];
    return accessToken;
}

// Method to generate a JWT signature for Zoom Meeting

public String generateJWT(String meetingNumber, String role) {
try {
// Current time and expiration
long nowMillis = System.currentTimeMillis();
Date now = new Date(nowMillis);
Date exp = new Date(nowMillis + 10 * 60 * 1000); // 10 minutes expiration

        // Build JWT claims
        JWTClaimsSet claims = new JWTClaimsSet.Builder()
                .claim("sdkKey", clientId)
                .claim("appKey", clientId)
                .claim("mn", Long.parseLong(meetingNumber))
                .claim("role", Integer.parseInt(role))
                .issueTime(now)
                .expirationTime(exp)
                .claim("tokenExp", exp.getTime() / 1000) // tokenExp in seconds
                .build();

        // Create HMAC signer
        JWSSigner signer = new MACSigner(clientSecret.getBytes());

        // Prepare JWS object with HS256 algorithm
        SignedJWT signedJWT = new SignedJWT(new JWSHeader(JWSAlgorithm.HS256), claims);

        // Apply the HMAC protection
        signedJWT.sign(signer);

        // Serialize to compact form, this is your JWT token (signature)
        return signedJWT.serialize();

    } catch (Exception e) {
        e.printStackTrace();
        return null;
    }
}

// Method to generate the JWT signature using HMAC SHA256
private String HmacSHA256(String payload, String key) {
    try {
        Mac sha256_HMAC = Mac.getInstance("HmacSHA256");
        SecretKeySpec secret_key = new SecretKeySpec(key.getBytes(), "HmacSHA256");
        sha256_HMAC.init(secret_key);

        byte[] bytes = sha256_HMAC.doFinal(payload.getBytes());

        return Base64.getEncoder().encodeToString(bytes);
    } catch (Exception e) {
        e.printStackTrace();
        return null;
    }
}

}