Zoom LTI Pro - Error 2231

API Endpoint(s) and/or Zoom API Event(s)
https://applications.zoom.us/api/v1/lti/rich/open/meeting/list

Description
I wrote a node TypeScript function that checks whether meetings tied to Moodle courses in the Zoom LTI Pro exist. The function takes a userId, which I have checked to ensure are valid Zoom user IDs. The function generates a signature that is validated correctly. In addition, the function returned success on Friday, and everything stayed the same over the weekend on my end.

Error?
{
status: false,
errorCode: ‘2231’,
errorMessage: ‘Something went wrong. Please refresh this page.’
}

How To Reproduce

async function getMoodleCourseMeetingList(userId: string): Promise<GetCourseMeetingListResponseModel | void> {
  const timestamp = new Date().getTime()
  const ltiKey = process.env.ZOOM_LTI_KEY
  const ltiSecret = process.env.ZOOM_LTI_SECRET
  const ltiSignature = `isUpComing=true&key=${ltiKey}&pageNum=1&pageSize=20&timestamp=${timestamp}&userId=${userId}`
  const utf8ltiSignature = CryptoJS.enc.Utf8.parse(ltiSignature)
  const hmacString = CryptoJS.HmacSHA1(utf8ltiSignature, ltiSecret)
  const ltiSignatureHash = CryptoJS.enc.Base64url.stringify(hmacString)
  
  const url = `${baseUrl}/open/meeting/list?${ltiSignature}`

  const headers = {
    'X-Lti-Signature': ltiSignatureHash
  }

  try {
    const response = await Axios({
      method: 'get',
      url,
      headers
    })
    console.log(response.data)
    return response.data
  } catch (error) {
    console.error(error)
    return
  }
}```

Following up, I redid the signature so the signature result now matches the examples here:
Google App Script example
Powershell example

In addition, I had to create a new credential using LTI 1.3, for the bulk import to work. Unfortunately, the meeting list endpoint still throws the same error.

Here is the function that creates the signature:

async function generateSignature(message: string) {
  const crypto = require('crypto');
  let ltiSecret = process.env.ZOOM_LTI_SECRET;

  let hmac = crypto.createHmac('sha1', ltiSecret);
  hmac.update(message);
  let signature = hmac.digest('base64');
  signature = signature.split('=')[0].replace(/\+/g, '-').replace(/\//g, '_');
  
  return signature;
}

Here is the function that fetches the meeting list. Still getting the same error 2231:

async function getMoodleCourseMeetingList(userId: string): Promise<GetCourseMeetingListResponseModel | void> {
  const timestamp = new Date(new Date().toUTCString()).getTime()
  const ltiKey = process.env.ZOOM_LTI_KEY
  const message = `isUpComing=true&key=${ltiKey}&pageNum=1&pageSize=20&timestamp=${timestamp}&userId=${userId}`
  const ltiSignatureHash = await generateSignature(message)
  
  const url = `${baseUrl}/open/meeting/list?${message}`

  const headers = {
    'X-Lti-Signature': ltiSignatureHash
  }

  try {
    const response = await Axios({
      method: 'get',
      url,
      headers
    })
    console.log(response.data)
    return response.data
  } catch (error) {
    console.error(error)
    return
  }
}

Here is the function that adds the meetings to Moodle (Bulk import API). It is working, but adding the userId, as per the documentation, does not work:

async function associateZoomMeetingWithCourse(meetingList: ZoomLtiRequestBodyType['meetingList']): Promise<any | void> {
  // const userId = process.env.ZOOM_USER_ID // Adding the userId doesn't work for me
  const timestamp = new Date(new Date().toUTCString()).getTime()
  const ltiKey = process.env.ZOOM_LTI_KEY
  const message = `key=${ltiKey}&timestamp=${timestamp}` // &userId=${userId}` // Adding the userId doesn't work
  const ltiSignatureHash = await generateSignature(message)

  console.log('ltiSignatureHash', ltiSignatureHash)
  const url = `${baseUrl}/meeting/bulkImport?${message}`
  console.log(url)

  const headers = {
    'X-Lti-Signature': ltiSignatureHash,
    'Content-Type': 'application/json',
    'Accept-Encoding': 'gzip, deflate, br',
    'Accept': 'application/json',
  }

  if (meetingList.length > 400) throw new Error('Too many meetings to associate with course. (Max 400)')

  const data = meetingList // Adding the userId doesn't work

  try {
    const response = await Axios({
      method: 'post',
      url,
      headers,
      data
    })
    console.log(response.data)
    return response.data
  } catch (error) {
    console.error(error)
    return
  }
}