OAuth 2.0

View as Markdown

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.

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.

KeyValue
grant_typeaccount_credentials
account_idYour 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.

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.

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.

KeyValue
tokenYour 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:

URLMethods
/v2/users/meGET, PATCH
/v2/users/me/tokenGET
/v2/users/me/meetingsGET, 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

  1. Open your app and navigate to App Credentials under Basic Information.
  2. Toggle Use Public Client OAuth to on.
  3. Copy the public client ID and send it, Base64 encoded, for user authorization. It does not require a client secret.

PKCE authorization flow

Generate a code_verifier, a cryptographically random string, locally in your client. Derive a code_challenge from it. For S256, take the SHA-256 hash of the code_verifier and Base64URL-encode it. Use your platform's crypto library, for example CryptoKit on iOS, MessageDigest on Android, or crypto.subtle in browsers.

Send the user to the authorization URL with your public client ID, code_challenge, and code_challenge_method:

https://zoom.us/oauth/authorize?response_type=code&client_id=ZOOM_PUBLIC_CLIENT_ID&redirect_uri=REDIRECT_URI&code_challenge=CODE_CHALLENGE&code_challenge_method=S256

After the user clicks Allow on the consent screen, Zoom redirects to your redirect_uri with an authorization code.

To exchange the code for an access token, send a POST request to https://zoom.us/oauth/token. Unlike the confidential client flow, PKCE does not use an Authorization header. Send all fields in the request body as application/x-www-form-urlencoded.

KeyValue
grant_typeauthorization_code
codeThe authorization code from the redirect.
client_idYour public client ID.
redirect_uriThe same redirect URI used in the authorization request.
code_verifierThe original code_verifier.

If successful, the response body contains the access_token, refresh_token, and a token_type of bearer. You can now make authorized API requests.

PKCE parameter reference

See the Proof Key for Code Exchange by OAuth Public Clients  RFC for full details.

KeyDescription
code_verifierA cryptographically random string generated locally by the client. Sent in the access token request so Zoom can validate the previously received code_challenge.
code_challengeA challenge derived from the code_verifier. Sent in the authorization request.
code_challenge_methodThe method used to derive the code_challenge, either S256 or plain. Sent in the authorization request. Defaults to plain if not present. Public clients must use S256.

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.

See also

Supporting concurrent logins

With confidential clients, a backend server can negotiate access tokens across multiple clients of the same end user account, if the developer wishes to support this concurrent client use case. When using a public client, there is no backend server to manage these tokens, since the clients communicate directly with the authentication APIs.

To address this use case, Zoom enables a feature called "multi-auth token" automatically when you enable a public client ID in the build flow. Multi-auth token mode allows any valid access token for the user to call Zoom APIs, not just the most recently-issued one.

Using a loopback redirect URI

Native applications, desktop clients, CLI tools, and local development environments, can use a loopback redirect URI to receive the OAuth authorization response without exposing a publicly reachable endpoint. This approach follows the security recommendations in RFC 8252, OAuth 2.0 for Native Apps .

With a loopback redirect, your application starts a temporary local HTTP server, directs the user's system browser to Zoom's authorization endpoint, and captures the authorization code when the browser redirects back to http://127.0.0.1:{port}/{path}. To implement, create the HTTP server first (e.g. on port 0), read the assigned port, then open the browser.

Eligibility

  • Use loopback for PKCE-enabled clients and native app clients. Zoom matches loopback redirect URIs while ignoring only the port. The scheme, numeric loopback host, path, query, fragment, and user info must otherwise match the registered URI exactly.
  • Do not use loopback for simple OAuth apps that are not PKCE-enabled and native apps. These must continue to use HTTPS redirect URIs. The user-facing error from the OAuth authorize endpoint is: Invalid redirect: <redirectUri>.

When to use loopback redirection

Use this method when your application runs locally on the user's machine and cannot receive inbound HTTPS callbacks from the internet. Common scenarios include:

  • Desktop applications (Electron, native GUI apps)
  • Command-line interface (CLI) tools
  • Local development and testing environments

Prerequisites

  • A registered Zoom Marketplace OAuth app.
  • Proof Key for Code Exchange (PKCE) enabled — required for all public/native clients.
  • A loopback redirect URI registered in your app's OAuth allow list.

Create a loopback redirect URI

  1. Generate a PKCE code verifier and challenge. Create a cryptographically random code_verifier (43–128 characters) and derive the code_challenge using the S256 method.

  2. Start the local HTTP server - Bind to 127.0.0.1:0 to get an ephemeral port.

  3. Read the assigned port - Get the actual port from the server (e.g., 52936). The port may vary on each run.

  4. Construct the redirect URI. Build the URI using the loopback IP literal and the selected port:

    http://127.0.0.1:52936/callback
    
  5. Launch the system browser. Open the user's default browser to Zoom's authorization endpoint with the required parameters:

    https://zoom.us/oauth/authorize?response_type=code
    &client_id=YOUR_CLIENT_ID
    &redirect_uri=http://127.0.0.1:52936/callback
    &code_challenge=YOUR_CODE_CHALLENGE
    &code_challenge_method=S256
    &state=YOUR_RANDOM_STATE
    
  6. Capture the redirect. After the user authorizes, Zoom redirects the browser to your local listener. Extract the code and state query parameters from the incoming request.

  7. Validate the state parameter. Confirm that the returned state matches the value you generated in step 4.

  8. Exchange the code for tokens. Send a POST request to the token endpoint with the authorization code, redirect URI, and PKCE code verifier:

    POST https://zoom.us/oauth/token
    Content-Type: application/x-www-form-urlencoded
    
    grant_type=authorization_code
    &client_id=YOUR_CLIENT_ID
    &code=AUTHORIZATION_CODE
    &redirect_uri=http://127.0.0.1:52936/callback
    &code_verifier=YOUR_CODE_VERIFIER
    
  9. Shut down the listener. Close the local HTTP server immediately after receiving the redirect.

Register a loopback redirect URI

Register the loopback redirect URI with or without a port with the same scheme, numeric loopback host, and path your app will use (for example,127.0.0.1 and [::1] are both supported for eligible clients, but they are matched as different hosts). Do not use localhost. Register and send numeric loopback literals. These formats are accepted for PKCE or native-style clients:

http://127.0.0.1
http://127.0.0.1:3000
http://127.0.0.1/callback
http://127.0.0.1:8080/callback
http://[::1]
http://[::1]:60123/callback

Other considerations

  • Use 127.0.0.1, not localhost. The loopback IP literal avoids DNS resolution issues and prevents unintended network exposure. RFC 8252 explicitly recommends against using localhost for this reason.
  • Bind only to the loopback interface. Never bind the listener to 0.0.0.0 or a public interface.
  • Close the listener immediately. Shut down the local HTTP server as soon as you receive the authorization response to minimize the window of exposure.
  • Always use PKCE. The code verifier and challenge mechanism prevents authorization code interception attacks, which are especially relevant for native apps where the client secret cannot be kept confidential.
  • Validate the state parameter. Always compare the returned state to the value you generated to prevent CSRF attacks.
  • Handle port conflicts gracefully. If the initially selected port becomes unavailable, retry with a different ephemeral port before presenting an error to the user. For example, port already in use errors, redirect URI mismatch errors (wrong host, wrong path), client not eligible for loopback (not PKCE-enabled), browser not opening or user closing browser before authorization.
  • Do not cache or reuse the listener port. Select a fresh ephemeral port for each authorization attempt.
  • Rate limits. There are no rate limits per loopback.
  • No maximum redirect attempts. There is no explicit maximum number of redirect attempts before an authorization code expires.