Authorization and deep linking

In this section, you'll implement the installation and authorization flows to install a chatbot in a Zoom workspace, authenticate API requests, and deep link users between a browser-based application and the Zoom desktop client. This enables seamless transitions from SaaS web apps to native collaboration environments.

By the end of this section, you’ll have:

  • A secure Chat integration using OAuth 2.0
  • Seamless deep linking between the browser and Zoom client
  • Reusable server endpoints for message delivery and future features

Implement OAuth 2.0 authentication

Add two routes to handle the OAuth 2.0 flow: one to initiate login and another to process the callback from Zoom.

EndpointDescription
/loginRedirects users to Zoom's OAuth consent screen using the app's client ID and redirect URI
/callbackHandles the redirect from Zoom, validates the state parameter, exchanges the authorization code for an access token, and stores tokens in the session for later use

File Path

routes/oauth-routes.js

Code Snippet

import express from "express";
import crypto from "crypto";
import {
    buildBasicAuth,
    exchangeCodeForAccessToken,
} from "../utils/zoom-api.js";
const router = express.Router();
// OAuth: start
router.get("/login", (req, res) => {
    try {
        const state = crypto.randomBytes(16).toString("hex");
        req.session.oauth_state = state;
        const url = buildBasicAuth({
            clientId: process.env.ZOOM_CLIENT_ID,
            redirectUri: process.env.ZOOM_REDIRECT_URI,
            state,
        });
        console.log("Redirecting to Zoom OAuth URL:", url);
        return res.redirect(url);
    } catch (e) {
        console.error("OAuth login error:", e);
        return res.status(500).send("OAuth not configured.");
    }
});
// OAuth: callback
router.get("/callback", async (req, res) => {
    try {
        const { code, state, error, error_description } = req.query;
        if (error) {
            console.warn("Zoom authorization error:", error, error_description);
            return res.status(400).send(`Authorization error: ${error}`);
        }
        if (!code || !state)
            return res.status(400).send("Missing code or state.");
        if (state !== req.session.oauth_state)
            return res.status(400).send("Invalid state.");
        delete req.session.oauth_state; // one-time use
        const tokens = await exchangeCodeForAccessToken({
            code,
            redirectUri: process.env.ZOOM_REDIRECT_URI,
            clientId: process.env.ZOOM_CLIENT_ID,
            clientSecret: process.env.ZOOM_CLIENT_SECRET,
        });
        // Demo: store tokens in session (use DB/secret store in prod)
        req.session.zoomTokens = tokens;
        // Option A: redirect to a confirmation page with a button to open in Zoom
        return res.redirect("/dashboard");
        // Option B: redirect straight into Zoom via a JID deep link:
        // return res.redirect(process.env.ROBOT_ZOOM_BOT_JID || '[https://zoom.us/launch/chat?jid=robot_example@xmpp.zoom.us](https://zoom.us/launch/chat?jid=robot_example@xmpp.zoom.us)');
    } catch (e) {
        console.error("OAuth callback error:", e);
        return res.status(500).send("Token exchange failed.");
    }
});
export default router;

Add deep linking to the chatbot

After the user authorizes your app, use a deep link to open the chatbot directly in Chat.

File path

views/dashboard.html

Code snippet

<script>
  const chatbotJid = "robot_v1nhrah1hrqhuexexb5nn5-a@xmpp.zoom.us"; // Replace with your bot JID
  document.getElementById("zoom-button").addEventListener("click", () => {
    const link = `https://zoom.us/launch/chat?jid=${encodeURIComponent(chatbotJid)}`;
    window.open(link, "_blank", "noopener,noreferrer");
  });
</script>

Transition from browser-based app to Zoom client

Use this pattern to create a smooth transition from your browser-based app to the Zoom client, enhancing user experience after authorization.