JWT Documentation

In the section Quickstart - JWT with Zoom there is a paragraph: “Generate your JWT Token using Sever Side Code”

Underneath there are 6 tabs, each illustrating this process using Node, cURL, PHP, Python, Ruby and C#

Only the node example actually generates the JWT and makes a call with it.
All the other language/methods listed do not show the creation of the JWT and automatically assume the user already has the bearer token.

Either the node example should not show the creation of the JWT or (preferably) the other examples should show the JWT acquisition in their respective languages so each example is truly equivalent. It makes no sense to show the full process for node and half the process for the others in the neighboring tabs, it just confuses.

Here is the Python example extended to include the JWT creation:

import jwt   # pip install pyjwt
import http.client
import datetime
import json

api_key = '<api key goes here>'
api_sec = '<secret goes here>'

# generate JWT
payload = {
'iss': api_key,
'exp': datetime.datetime.now() + datetime.timedelta(hours=2)
jwt_encoded = jwt.encode(payload, api_sec)

# call API: get user list
conn = http.client.HTTPSConnection("api.zoom.us")
headers = {
'authorization': "Bearer %s" % jwt_encoded,
'content-type': "application/json"
conn.request("GET", "/v2/users?status=active", headers=headers)
res = conn.getresponse()
response_string = res.read().decode('utf-8')
response_obj = json.loads(response_string)
1 Like

Hi @Rick1975,

Thanks for the feedback. We are working on your suggestions and will get back to you with an update later.


Hi @Rick1975, thanks a lot for bringing this to our attention. We have now changed the section you mentioned so that it shows how to make API calls using the JWT generated from JWT.io to make it consistent as you suggested. In the future, we will be adding the complete code for generating JWT in multiple languages. For now, we have a working Node.js sample app tutorial in the Quickstart page.

Thank you for helping make our docs better. :+1:


In case it may be useful to someone else, I had to do the following to make this work

jwt_encoded = str(jwt.encode(payload, api_sec), ‘utf-8’)

By the way THANK YOU so much for this code. It was very helpful to me!!!

Thanks for posting Allison!

1 Like

Thank you for posting this. Spend 2 days trying to figure it out. Not a fan of using 3rd party libraries but could not find any other way of doing it.

For those that struggle with the same :

require_once "vendor/autoload.php";
use Firebase\JWT\JWT;

$api_key = 'your_api_key_here';
$api_secret = 'your_api_secret_key_here';
$issuedAt = time();
$expirationTime = $issuedAt + 60;  // jwt valid for 60 seconds from the issued time
$payload = array(
    'iss' => $api_key,
    'exp' => $expirationTime

$jwt = JWT::encode($payload, $api_secret);

print_r('JWT Token : ' . $jwt . PHP_EOL);

Thanks for including this @arjan.sinnige!

Did this ever happen? I cannot find such code.

Hey @laurinkeithdavis,

Not yet, however we do recommend any of the libraries on jwt.io to generate your JWT Token.


Yes, thanks, and I’ve tried that, but there things in the Zoom API documentation that do not match up with any of those libraries I have tried.

Hey @laurinkeithdavis,

Anything specific? We would love to hear so we can improve our docs! :slight_smile:


After a bunch of 401s, I realized you need to use UTC time so use .utcnow instead of .now shown in the example.

Change it to this,

‘exp’: datetime.datetime.utcnow() + datetime.timedelta(hours=2)

or use this,

import datetime
import jwt

def generate_token(api_key, api_sec):
payload = {
‘iss’: api_key,
‘exp’: datetime.datetime.utcnow() + datetime.timedelta(hours=2),
token = str(jwt.encode(payload, api_sec), ‘utf-8’)
return token

Hey @BenjaminS thanks for sharing that.

Sharing additionally, I’ve used this in most of my projects:

import jwt
from time import time
import secrets # holds API Key / Secret

def generateToken():
    token = jwt.encode(
        # Create a payload of the token containing API Key & exp time
        {"iss": secrets.API_KEY, "exp": time() + 5000},
        # Secret used to generate token signature
        # Specify the hashing alg
        # Converts token to utf-8

    return token

We’ll update the samples on the JWT Docs (they’ve been updated significantly since this original post (April 2019))

Yes, but unfortunately, I’ve spent all the time I can on this project for the foreseeable future (since I can’t use it anyway).

However, the biggest issue was a complete misconception of how tokens are generated. It looked like the token was generated by the vendor via a URL that the library contacted, but I see now the token is generated by an algorithm from the library itself (locally) using key + secret provided

Compounding my confusion is the differing use of the ‘iss’ - from the libraries - in the library examples, the iss is a URL, but in the Zoom API, it’s the API key - why the standard allows for this variance blows my mind. I wrote code like a long time ago until I realized that it can cause terrible confusion in the future, which it did here (no fault of Zoom, but I sure wish that standard was rewritten).

Thanks for your feedback @laurinkeithdavis.

We will work on making our docs more clear for the iss field.


@michael.harrington I’m somewhat new to Python. Can you elaborate on using secrets to hold the API Key? All the info I have found says that secrets is for generating secure random numbers.


Hi @tribloom, when putting up sample code, I typically add a secrets.py file I can grab all secret credentials which I can add in a .gitignore file so I never accidentally share any secret credentials publicly.

That makes sense. Thanks for the quick response.


Of course, let us know if we can help anywhere!