Zoom LTI Pro Bulk Import API usage

At UoM, we are attempting to use the Zoom LTI Pro Bulk import API to bulk create Zoom calendar entries in Canvas. I have been trying the instructions provided in the Bulk Import page ( POST /meeting/bulkImport) for several days now without much success.

We have imported the LTI Pro API json collection to Postman for our testing(https://zoomappdocs.docs.stoplight.io/lti-pro-v2/LTIPro-API/LTI%20Pro%20API.oas2.json). The bulk import API method does not require access token or any other authentication method, therefore no access token was used, except for constructing the X-Lti-Signate as explained below.

When I attempt to call the bulk import API from Postman, I am getting the following error response from the webservice.

“status”: false,
“errorCode”: “401”,
“errorMessage”: “Verify LTI signature failed(2203).”

Which Endpoint/s?

How To Reproduce (If applicable)
Steps to reproduce the behavior:

  1. Construct the base string using key=lti_key&timestamp=1639693478230
    timestamp is milliseconds since epoch based on AEST. lti_key is found in the app registration page
  2. Use the secret and the base string to get a HMAC-SHA1 string. I used a free online hmac generator for this step
  3. Use the calculated HMAC-SHA1 string from above and encode it to base 64 safe url string (again, I used an on line tool for this)
  4. Use the converted string from step 4 in X-Lti-Signature header field
  5. The request body is populated with appropriate field values

Note: The Zoom LTI pro has been successfully integrated with Canvas since I am able to manually create/view Zoom meetings within Canvas. I have also used the csv import within the app and was able to successfully import meetings using the CSV. The issue is only with the bulk import API.

Additional context
The issue must be related to how I am constructing the X-Lti-Signatrue. I am wondering if any of the members in this forum has successfully used the above API to automate calendar entry creation in Canvas and if so if they would mind sharing any tips on how to do this.

Hi @nwijesundera ,

Please open a ticket with Zoom Support for LTI Pro. They are best equipped to assist with this :slight_smile:


1 Like

Were you able to get the proper construction of the LTI Pro API header?

Hi Jeff, Unfortunately no, I am still working with a Zoom technical resource to try and work this out. I will post something as soon as I have some luck.

1 Like

I was able to get this working in Google App Script:

const importMeetingIntoMoodle = (meetingId, courseId) => {
const timestamp = Date.now();
const payload = [{
meetingId: meetingId,
contextId: courseId.trim(),
domain: DOMAIN
const headers = {
“X-Lti-Signature”: getLtiSignature(timestamp)
return UrlFetchApp.fetch(`https://applications.zoom.us/api/v1/lti/rich/meeting/bulkImport?key=${ZOOM_LTI_KEY}&timestamp=${timestamp}`,
method: ‘POST’,
contentType: ‘application/json’,
headers: headers,
payload: JSON.stringify(payload)

const getLtiSignature = (timestamp) => {
const signatureBaseString = key=${ZOOM_LTI_KEY}&timestamp=${timestamp}
const signature = Utilities.computeHmacSignature(Utilities.MacAlgorithm.HMAC_SHA_1,signatureBaseString, ZOOM_LTI_SECRET);
return encodeURIComponent(Utilities.base64Encode(signature).replace(/=+$/, ‘’));

As you can see, you have to encode the signature, remove the trailing equals sign (=), then encode to a URL safe string. Shoot me a reply if you have questions.

Got it working in PowerShell. If it’s helpful to anyone:

$lti_key = 'from marketplace LTI Pro app'
$lti_secret = 'from marketplace LTI Pro app'

# Based on the super helpful algorithm script here: https://www.reddit.com/r/PowerShell/comments/8bc3rb
$exp = [int][double]::parse((Get-Date -Date $((Get-Date).ToUniversalTime()) -UFormat %s)) # Grab Unix Epoch Timestamp
$ToBeSigned = 'key=' + $lti_key + '&timestamp=' + $exp +'000'
$SigningAlgorithm = New-Object System.Security.Cryptography.HMACSHA1
$SigningAlgorithm.Key = [System.Text.Encoding]::UTF8.GetBytes($lti_secret)
$Signature = [Convert]::ToBase64String($SigningAlgorithm.ComputeHash([System.Text.Encoding]::UTF8.GetBytes($ToBeSigned))).Split('=')[0].Replace('+', '-').Replace('/', '_')

$headers.Add("X-Lti-Signature", "$Signature")
$headers.Add("Content-Type", "application/json")

$meetingID = "Zoom meeting ID, no spaces"
$contextID = "from your LMS course"

$APIbody = "[{`"meetingId`": `"" + $meetingID + "`",`"contextId`": `"" + $contextID + "`",`"domain`": `"https://yourlmsdomain.com`"}]"

$uri = 'https://applications.zoom.us/api/v1/lti/rich/meeting/bulkImport?key=' + $lti_key + '&timestamp=' + $exp + '000' 
Invoke-RestMethod $uri -Method 'POST' -Headers $headers -ContentType 'application/json' -Body $APIbody

#$uri = 'https://applications.zoom.us/api/v1/lti/rich/meeting/bulkDisassociate?key=' + $lti_key + '&timestamp=' + $exp + '000' 
#Invoke-RestMethod $uri -Method 'PUT' -Headers $headers -Body $APIbody

Thanks Jeff, great work! I managed to get it working in our test environment. However, I am still struggling to see the Zoom calendar entry in the Canvas subject. I can see the meeting in the Zoom app within the Canvas subject but not the entry within the Calendar.

I notice that you have not included the courseId field in the payload but I have added this in with my testing. I have used all of the following permutations for the courseId without much luck.

  • The numeric course id e.g. 115149
  • Sis course id e.g. ABPL30041_2022_SM1
  • The big int course id e.g. 154000000000115149

Let me know if have any luck with this. I am also interested to know who you managed to find the contextId for your Canvas subject. In my case I inspected the page displayed for the Zoom app and searched for the contextId hidden input field.


@AVJeff Thanks for sharing what worked for you! Are you able to able to share answers to @nwijesundera’s questions?

Hi Narada,
It’s been a while since I’ve worked with Canvas; in this case, I was integrating with Blackboard.
I also initially found the contextID via the browser console. Then, we found that field in our Blackboard database and exported the IDs from there. You may be able to do the same from the Canvas database.
You didn’t see anything related to courseID in the browser console? Again, I’m not using Canvas (and Blackboard doesn’t have that calendar integration), but I do see some possible entries like “ext_fnds_course_id”. I’m not sure if you’ll also have those same entries in Canvas to try. Perhaps Zoom Support could help you find the appropriate field from Canvas or at least give you an example of what the LTI API needs for that field.
Good luck!

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