OAuth 2.0
OAuth 2.0 lets your app securely access Zoom APIs for users or accounts. Zoom supports industry-standard OAuth 2.0 (rfc6749) flows to handle authorization and access tokens.
Your app can request, refresh, and revoke tokens based on its type and authorization method. Tokens expire after a set time and may need renewal or refresh.
Note
Zoom API library for Rivet handles OAuth for you. For more information, go to Zoom Rivet → Javascript → Get started → Initialize the client.
Request an access token
Zoom offers OAuth 2.0 flows and grant types for these four authorization use cases.
App Type: Server to Server
Get an access_token to call APIs for your own Zoom account.
To request an access token, send a POST request to https://zoom.us/oauth/token. Include a basic authorization header and provide the following required parameters either as query parameters or in the request body using the x-www-form-urlencoded format.
| Key | Value |
|---|---|
grant_type | account_credentials |
account_id | Your Zoom account ID, found on the Server to Server app build page. |
The basic authorization header is your Client ID and Client Secret with a colon : in between, Base64 Encoded.
Example request
POST https://zoom.us/oauth/token?grant_type=account_credentials&account_id=Wk9PTV9BQ0NPVU5UX0lE
Request headers
{ "Authorization": "Basic Wk9PTV9DTElFTlRfSUQ6Wk9PTV9DTElFTlRfU0VDUkVU" }
If successful, the response body is a JSON representation of the access_token.
{
"access_token": "<JWT_TOKEN>",
"token_type": "bearer",
"expires_in": 3600,
"scope": "user:read:user:admin",
"api_url": "https://api.zoom.us"
}
Access tokens expire after one hour. After your access token expires, you can request a new one. Zoom does not require a separate refresh flow.
App Type: General
Get an access_token to call APIs on behalf of a Zoom user or Zoom account.
Get a user authorization code
You can get a user authorization code by adding your app to your Zoom account. To do this, navigate to the Local Test page, and choose Add App Now.
Alternatively, construct the authorization URL using the following format:
https://zoom.us/oauth/authorize?response_type=code&client_id=ZOOM_CLIENT_ID&redirect_uri=REDIRECT_URI
See section 4.1.1 of The OAuth 2.0 Authorization Framework RFC for details.
User authorization flow
- When the user visits this URL, Zoom prompts them to sign in (if not already signed in) and to grant the requested permissions.
- After the user authorizes the app, Zoom redirects the user’s browser to the specified
redirect_uriand includes an authorization code. For example,https://example.com/callback?code=AUTH_CODE. - Your application should capture this authorization code from the redirect request and exchange it for an
access_tokenusing the OAuth token endpoint. - If this is the user's first time authorizing your app, or if you've updated scopes or webhook settings, Zoom will prompt the user to authorize again before issuing a new authorization code.
Exchange authorization code for access token
Once you have the authorization code from the user in the redirect URI, request an access token by sending a POST request to https://zoom.us/oauth/token with a Basic Authorization header.
Include the following required parameters either as query parameters or in the request body using the x-www-form-urlencoded format.
| Key | Value |
|---|---|
grant_type | authorization_code |
code | The code in the redirect URI after a successful user authorization. |
redirect_uri | Your redirect URI. |
state | (Optional) A string used to pass state between the request and callback. This can help prevent cross-site request forgery. |
The Basic Authorization header is your Client ID and Client Secret by a colon (:) and Base64 encoded. Use PKCE to enable user authorization with a public client ID an no client secret, without the need for a backend server. See Using Proof Key for Code Exchange (PKCE) for details.
Example request
POST https://zoom.us/oauth/token?grant_type=authorization_code&code=Wk9PTV9BVVRIT1JJWkFUSU9OX0NPREU&redirect_uri=https://example.com
Request headers
{
"Authorization": "Basic Wk9PTV9DTElFTlRfSUQ6Wk9PTV9DTElFTlRfU0VDUkVU"
}
If successful, the response body contains a JSON representation of the access_token.
{
"access_token": "<JWT_TOKEN>",
"token_type": "bearer",
"refresh_token": "<JWT_TOKEN>",
"expires_in": 3600,
"scope": "user:read:user",
"api_url": "https://api.zoom.us"
}
Access tokens expire after one hour. After your access token expires, you can follow the refresh flow to get a new one.
App Type: General
Get an access_token from a device flow to call APIs on behalf of a Zoom user or Zoom account.
In your app build flow settings, navigate to Features > Embed > Enable Meeting SDK > Enable Use App on Device.
To request a device code, user code, and verification URI, make a POST request with a basic authorization header to:
https://zoom.us/oauth/devicecode?client_id=ZOOM_CLIENT_ID
The basic authorization header is your Client ID and Client Secret with a colon : in between, Base64 Encoded.
Example request
POST https://zoom.us/oauth/devicecode?client_id=Wk9PTV9DTElFTlRfSUQ
If successful, the response body is a JSON representation of the device code, user code, and verification URI.
{
"device_code": "Wk9PTV9ERVZJQ0VfQ09ERQ",
"user_code": "abcd1234",
"verification_uri": "https://zoom.us/oauth_device",
"verification_uri_complete": "https://zoom.us/oauth/device/complete/Wk9PTV9WRVJJRklDQVRJT05fVVJJX0NPTVBMRVRF",
"expires_in": 900,
"interval": 5
}
Ask the user to navigate to the verification URI and display the user code. Alternatively, direct the user to the complete verification URI, which contains prefilled user code. Prompt the user to sign in if the user is not signed in to Zoom.
After entering the code, the user receives a prompt to allow the app. Once allowed, the app's landing page appears. The user receives a notification that the process can continue on the device.
You can now request an access token.
To request an access token, send a POST request to https://zoom.us/oauth/token with a basic authorization header. Include the following required parameters either as query parameters or in the request body using the x-www-form-urlencoded format.
| Key | Value |
|---|---|
grant_type | urn:ietf:params:oauth:grant-type:device_code |
device_code | The device_code returned in the previous step. |
The basic authorization header is your Client ID and Client Secret with a colon : in between, Base64 Encoded.
Example request
POST https://zoom.us/oauth/token?grant_type=urn:ietf:params:oauth:grant-type:device_code&device_code=Wk9PTV9ERVZJQ0VfQ09ERQ
Request headers
{ "Authorization": "Basic Wk9PTV9DTElFTlRfSUQ6Wk9PTV9DTElFTlRfU0VDUkVU" }
If successful, the response body is a JSON representation of the access_token.
{
"access_token": "<JWT_TOKEN>",
"token_type": "bearer",
"refresh_token": "<JWT_TOKEN>",
"expires_in": 3599,
"scope": "user:read:user user:read:token",
"api_url": "https://api.zoom.us"
}
Access tokens expire after one hour. After your access token expires, you can follow the refresh flow to get a new one.
App Type: General
Get an access_token to send, edit, and delete Chatbot messages.
To install your Chatbot to your Zoom account, navigate to the Local Test page, and choose Add App Now.
You can also construct and navigate to the authorization URL yourself following this format:
https://zoom.us/oauth/authorize?response_type=code&client_id=ZOOM_CLIENT_ID&redirect_uri=ZOOM_REDIRECT_URI
If this is the first time you are authorizing your app or if you have made webhook or scope changes, Zoom prompts you to authorize.
If authorized, you redirect to the redirect_uri with the authorization code in the code query parameter. You can use the code in the redirect URL for User Authorization OAuth to call additional API endpoints. If you only call Chatbot API endpoints, you do not need it.
https://example.com/?code=Wk9PTV9BVVRIT1JJWkFUSU9OX0NPREU
To request a chatbot token, send a POST request to https://zoom.us/oauth/token with a basic authorization header. Include the following query parameters or provide them in the request body using the x-www-form-urlencoded format.
| Key | Value |
|---|---|
grant_type | client_credentials |
The basic authorization header is your Client ID and Client Secret with a colon : in between, Base64 Encoded.
Example request
POST https://zoom.us/oauth/token?grant_type=client_credentials
Request headers
{ "Authorization": "Basic Wk9PTV9DTElFTlRfSUQ6Wk9PTV9DTElFTlRfU0VDUkVU" }
If successful, the response body is a JSON representation of the Chatbot token.
{
"access_token": "<JWT_TOKEN>",
"token_type": "bearer",
"expires_in": 3600,
"scope": "imchat:bot",
"api_url": "https://api.zoom.us"
}
Chatbot tokens expire after one hour. After your chatbot token expires, you can request a new one. Zoom does not require a separate refresh flow.
Refresh an access token
Here are the ways to refresh access tokens for these four authorization use cases.
App Type: Server to Server
Access tokens expire after one hour. After your access token expires, you can request a new one. Zoom does not require a separate refresh flow.
App Type: General
Access tokens expire after one hour. After your access token expires, you can follow the refresh flow to get a new one.
To refresh an access token, send a POST request to https://zoom.us/oauth/token with a basic authorization header. Include the following query parameters or provide them in the request body using the x-www-form-urlencodedformat.
| Key | Value |
|---|---|
grant_type | refresh_token |
refresh_token | Your refresh_token. |
The basic authorization header is your Client ID and Client Secret with a colon : in between, Base64 Encoded.
Example request
POST https://zoom.us/oauth/token?grant_type=refresh_token&refresh_token=<JWT_TOKEN>
Request headers
{ "Authorization": "Basic Wk9PTV9DTElFTlRfSUQ6Wk9PTV9DTElFTlRfU0VDUkVU" }
If successful, the response body is a JSON representation of the refreshed access_token.
{
"access_token": "<JWT_TOKEN>",
"token_type": "bearer",
"refresh_token": "<JWT_TOKEN>",
"expires_in": 3600,
"scope": "user:read:user",
"api_url": "https://api.zoom.us"
}
Refresh tokens expire after 90 days. You should always use the latest refresh token for the next refresh request. If your refresh token expires, you can direct the user to the authorize URL to restart the User OAuth flow.
App Type: General
Access tokens expire after one hour. After your access token expires, you can follow the refresh flow to get a new one.
To refresh an access token, send a POST request to https://zoom.us/oauth/token with a basic authorization header. Include the required parameters as query strings or in the request body using the x-www-form-urlencoded format.
| Key | Value |
|---|---|
grant_type | refresh_token |
refresh_token | Your refresh_token. |
The basic authorization header is your Client ID and Client Secret with a colon : in between, Base64 Encoded.
Example request
POST https://zoom.us/oauth/token?grant_type=refresh_token&refresh_token=<JWT_TOKEN>
Request headers
{ "Authorization": "Basic Wk9PTV9DTElFTlRfSUQ6Wk9PTV9DTElFTlRfU0VDUkVU" }
If successful, the response body is a JSON representation of the refreshed access_token.
{
"access_token": "<JWT_TOKEN>",
"token_type": "bearer",
"refresh_token": "<JWT_TOKEN>",
"expires_in": 3600,
"scope": "user:read:user user:read:token",
"api_url": "https://api.zoom.us"
}
Refresh tokens expire after 90 days. You should always use the latest refresh token for the next refresh request. If the refresh token expires, request a new device code, user code, and verification URI, then direct the user to the verification URI to restart the Device OAuth flow.
App Type: General
Chatbot tokens expire after one hour. After your chatbot token expires, you can request a new one. Zoom does not require a separate refresh flow.
Revoke an access token
App Type: All
To revoke an access token, send a POST request to https://zoom.us/oauth/revoke with a basic authorization header. Include the required parameters as query strings or in the request body using the x-www-form-urlencoded format.
| Key | Value |
|---|---|
token | Your access_token. |
The basic authorization header is your Client ID and Client Secret with a colon : in between, Base64 Encoded.
Example request
POST https://zoom.us/oauth/revoke?token=<JWT_TOKEN>
Request headers
{ "Authorization": "Basic Wk9PTV9DTElFTlRfSUQ6Wk9PTV9DTElFTlRfU0VDUkVU" }
If successful, the response body is a JSON representation of the revoked token.
{
"status": "success"
}
Use an access token
To call a Zoom API endpoint, make a request with your access_token bearer token.
Example request
GET https://api.zoom.us/v2/users/me
Request header
{ "Authorization": "Bearer <JWT_TOKEN>" }
If successful, the response body is a JSON representation of your user.
{
"id": "Wk9PTV9VU0VSX0lE",
"first_name": "Jane",
"last_name": "Dev",
"display_name": "Jane Dev",
"email": "jane.dev@example.com",
"type": 2,
"role_name": "Owner",
"pmi": 1234567890,
"use_pmi": false,
"personal_meeting_url": "https://janedev.zoom.us/j/1234567890?pwd=Wk9PTV9QTUlfUEFTU0NPREU",
"timezone": "America/Denver",
"verified": 1,
"dept": "",
"created_at": "2019-04-05T15:24:32Z",
"last_login_time": "2025-03-19T22:42:47Z",
"last_client_version": "6.4.0.51205(mac)",
"pic_url": "https://janedev.zoom.us/p/v2/Wk9PTV9QSUNfVVJM/Wk9PTV9QSUNfVVJM",
"cms_user_id": "",
"jid": "Wk9PTV9VU0VSX0lE@xmpp.zoom.us",
"group_ids": ["Wk9PTV9HUk9VUF9JRA"],
"im_group_ids": ["Wk9PTV9HUk9VUF9JRA"],
"account_id": "Wk9PTV9BQ0NPVU5UX0lE",
"language": "en-US",
"phone_country": "US",
"phone_number": "+1 1234567890",
"status": "active",
"job_title": "",
"cost_center": "",
"company": "Zoom",
"location": "",
"custom_attributes": [
{
"key": "Wk9PTV9DVVNUT01fQVRUUklCVVRFX0tFWQ",
"name": "Test",
"value": "set from API"
}
],
"login_types": [1, 100],
"role_id": "0",
"account_number": 12345678,
"cluster": "aw1",
"phone_numbers": [
{
"country": "US",
"code": "+1",
"number": "1234567890",
"verified": false,
"label": ""
}
],
"user_created_at": "2019-04-05T15:23:55Z"
}
Me context
You can replace the userID path parameter with me to target the user associated with the token. This approach lets you use user-specific endpoints without the need to look up or store the user ID for each token.
For example:
| URL | Methods |
|---|---|
/v2/users/me | GET, PATCH |
/v2/users/me/token | GET |
/v2/users/me/meetings | GET, POST |
Deauthorization
App Type: General
When a user deauthorizes or removes a production app, your app receives an HTTP POST request. The request goes to the deauthorization notification endpoint URL you specified on the App Listing page.
After receiving a deauthorization webhook event, apps must delete all associated user data.
An unsecured deauthorization notification endpoint URL leaves your server vulnerable to denial of service attacks. We recommend you verify the requests sent to your deauthorization notification endpoint URL with our supported webhook verification methods.
Below is an example deauthorization webhook event:
{
"event": "app_deauthorized",
"event_ts": 1740439732278,
"payload": {
"account_id": "Wk9PTV9BQ0NPVU5UX0lE",
"user_id": "Wk9PTV9VU0VSX0lE",
"signature": "Wk9PTV9TSUdOQVRVUkU",
"deauthorization_time": "2019-06-17T13:52:28.632Z",
"client_id": "XO8c5xFdQVqGAgGB3utlRA"
}
}
Using Proof Key for Code Exchange (PKCE)
The User Authorization use case also supports Proof Key for Code Exchange (PKCE). Use PKCE when you don't have a backend server for user authorization, such as on mobile devices. Zoom offers a separate public client ID in the authorization request that doesn't require an associated client secret.
Get your Public Client ID
- Open your app and navigate to App Credentials under Basic Information.
- Toggle Use Public Client OAuth to on.
- Copy the public client ID and send it, Base64 encoded, for user authorization. It does not require a client secret.
Using with client ID and secret
When you turn on public client OAuth, you can still use the
client ID:client secretbase64 encoded format with confidential client types.
Detailed justification for PKCE
If you make your app public, you may need to add an explanation why your application requires a public client ID in production. This helps our application review team test your PKCE integration and confirm your intended OAuth flow. Indicate whether you plan to use:
- The confidential client OAuth flow (for example, a web app with a backend),
- The public client OAuth PKCE flow (for example, a mobile or desktop app), or
- Both.
This information ensures our security team can validate your configuration according to your intended use.
Authorization URL query parameters
PKCE also offers the following optional parameters. See the Proof Key for Code Exchange by OAuth Public Clients RFC for details on how to use these.
| Key | Value |
|---|---|
code_verifier | A cryptographically random string used to correlate the authorization flow to the access token request, generated locally by the client. Not sent in the authorization request, but sent in the access token request so that Zoom can validate the previously received code_challenge. |
code_challenge | A challenge derived from the code_verifier and sent in the authorization request to verify against the code_verifier later. |
code_challenge_method | The S256 or plain method used to derive the code challenge. Sent in the authorization request Defaults to "plain" if not present in the request. |
Note
The OAuth consent page must always be displayed for public clients that do not use a client secret (RFC 6819, Section 5.2.3.2 – Public Clients). This prevents silent or automatic authorization that could expose user credentials or tokens to untrusted applications. This applies to the authorization code exchange step (
/oauth/token?grant_type=authorization_code). It's not applicable to the refresh token flow.
Here is an example of using PKCE with the Meeting SDK for Android.
See Introducing support for public PKCE OAuth for native and client-side apps on the developer blog for details.