# Build an AI-Powered Zoom Team Chatbot with Snowflake Cortex Agents (Node.js) If you followed [Dash](https://www.linkedin.com/in/dash-desai/)'s earlier post, you already know how easy it is to [set up Snowflake Cortex Agents within Zoom Team Chat](https://quickstarts.snowflake.com/guide/integrate-snowflake-cortex-agents-with-zoom/index.html#0). Now it's your turn. In this guide, you'll build a Zoom Teams Chat app that answers natural language questions with real-time insights from Snowflake Cortex — powered by Node.js. Fast to build, flexible to customize, and surprisingly fun to deploy. ## What You'll Build - A Zoom Team Chat bot that listens for messages from users - It forwards natural language queries to Snowflake Cortex - If Cortex returns SQL, the bot will execute the SQL - The SQL result will be explained back in natural language via Cortex Inference - It posts the final AI-generated answer right back into Zoom Chat ![Zoom Teams Chat Bot Architecture](/img/blog/ojussave/zoom-snowflake/architecture.png) ## Prerequisites Make sure you have: - A Zoom Account (Basic or better) - Zoom Desktop Client (Mac/Windows) - Snowflake Account (Cortex Agents enabled) - Node.js and npm installed - ngrok installed - A generated RSA private key (.p8 file) and public key set up in Snowflake ## Configure Your Environment Before running your bot, you need to set up your environment variables. Create a `.env` file in your project root. This file holds your credentials like Zoom keys, Snowflake API endpoints, and your private RSA key path. It keeps sensitive information separate from your code. ```plaintext ZOOM_ACCOUNT_ID="your-account-id" ZOOM_CLIENT_ID="your-client-id" ZOOM_CLIENT_SECRET="your-client-secret" ZOOM_REDIRECT_URI="https://your-ngrok-url.ngrok.io" ZOOM_BOT_JID="your-bot-jid" SNOWFLAKE_ACCOUNT="your-snowflake-account" SNOWFLAKE_AGENT_ENDPOINT="https://your-org-your-account.snowflakecomputing.com/api/v2/cortex/agent:run" SNOWFLAKE_INFERENCE_ENDPOINT="https://your-org-your-account.snowflakecomputing.com/api/v2/cortex/inference:complete" SUPPORT_SEMANTIC_MODEL="@dash_db.dash_schema.semantic_models/support_tickets_semantic_model.yaml" SUPPLY_CHAIN_SEMANTIC_MODEL="@dash_db.dash_schema.semantic_models/supply_chain_semantic_model.yaml" VEHICLE_SEARCH_SERVICE="dash_db.dash_schema.vehicles_info" RSA_PRIVATE_KEY_PATH="rsa_key.p8" ``` ## Zoom Team Chatbot Configuration For detailed instructions on setting up your Zoom Team Chatbot, refer to the [official Zoom documentation](/docs/team-chat/create-chatbot/). 1. Log into the Zoom App Marketplace and select to build a General (OAuth) app 2. Name your app something cool like "Znowflake Chatbot" 3. Set the Redirect URL to your ngrok forwarding URL 4. Under Select features, choose Team Chat: - Set your Bot Endpoint URL (your ngrok URL + /botendpoint) - Copy your Bot JID, you'll need it later 5. Make sure the following scope is selected: - imchat:bot 6. Save and continue 7. Go to the Local Test page on the Zoom App Marketplace 8. Select Add App Now and then Allow to add the app to your Zoom account 9. You should now see your chatbot inside Zoom Team Chat ## Set Up Snowflake For a comprehensive guide on setting up Snowflake for this integration, visit the [Snowflake Quickstart](https://quickstarts.snowflake.com/guide/integrate-snowflake-cortex-agents-with-zoom/index.html#1). Before your chatbot can query Snowflake Cortex, you need to set up your data and services inside Snowflake. ### Step 1: Clone the Setup Files Clone the GitHub repo containing Snowflake setup SQL scripts and sample semantic models. ```bash git clone https://github.com/zoom/zoom-teams-chat-snowflake-sample.git ``` > **Note:** If you are looking for the Python version, please visit [Snowflake Labs GitHub Repository](https://github.com/Snowflake-Labs/sfguide-integrate-snowflake-cortex-agents-with-zoom) ### Step 2: Create Database, Tables, and Stages 1. Open Snowsight (your Snowflake UI) 2. Create a new SQL Worksheet 3. Run setup.sql (provided in the repo) 4. This will: - Create the SUPPORT_TICKETS and SUPPLY_CHAIN tables with demo data - Create internal stages to store semantic model YAMLs and PDFs ### Step 3: Upload Semantic Models 1. In Snowsight, go to Data → Stages 2. Upload the semantic model YAML files to the DASH_SEMANTIC_MODELS stage ### Step 4: Upload PDF Documents 1. Upload the sample PDF files to the DASH_PDFS stage 2. These documents will be searchable using Cortex Search ### Step 5: Set Up Cortex Search Service 1. In a new Worksheet, run cortex_search_service.sql 2. This will create a Cortex Search service tied to your uploaded PDFs ### Step 6: Set Up Key-Pair Authentication 1. Generate an RSA private key (rsa_key.p8) if you haven't already 2. Upload the matching public key to your Snowflake user profile 3. Make sure your .env correctly points to your private key path ## Build the Node.js App ### Setup and Initialization ```javascript require("dotenv").config(); const express = require("express"); const axios = require("axios"); const fs = require("fs"); const jwt = require("jsonwebtoken"); const app = express(); app.use(express.json()); ``` ### Endpoint to Receive Messages ```javascript app.post("/botendpoint", async (req, res) => { const userMessage = req.body.payload.cmd; const toJid = req.body.payload.toJid; const finalAnswer = await queryCortexWithFallback(userMessage); await sendZoomMessage(toJid, finalAnswer); res.status(200).send("OK"); }); ``` ### Create Snowflake JWT Token ```javascript function createSnowflakeJwt() { const privateKey = fs.readFileSync(process.env.RSA_PRIVATE_KEY_PATH); const payload = { iss: `${process.env.SNOWFLAKE_ACCOUNT}/${process.env.SNOWFLAKE_ACCOUNT}`, sub: process.env.SNOWFLAKE_ACCOUNT, aud: "snowflake", iat: Math.floor(Date.now() / 1000), exp: Math.floor(Date.now() / 1000) + 3600, }; return jwt.sign(payload, privateKey, { algorithm: "RS256" }); } ``` ### Query Cortex and Handle Fallback Logic ```javascript async function queryCortexWithFallback(prompt) { const token = createSnowflakeJwt(); const agentResponse = await axios.post( process.env.SNOWFLAKE_AGENT_ENDPOINT, { prompt, tools: [ { type: "cortex_analyst_text_to_sql", semantic_model: process.env.SUPPORT_SEMANTIC_MODEL, }, { type: "cortex_search", search_service: process.env.VEHICLE_SEARCH_SERVICE, }, ], }, { headers: { Authorization: `Bearer ${token}` } }, ); const firstChoice = agentResponse.data.choices[0]?.message?.content; const sql = agentResponse.data.choices[0]?.message?.tool_use?.sql; if (sql) { const dataResponse = await axios.post( `https://${process.env.SNOWFLAKE_ACCOUNT}.snowflakecomputing.com/queries/v1/query-request`, { sqlText: sql, }, { headers: { Authorization: `Bearer ${token}` } }, ); const dataframe = dataResponse.data.data; const inferenceResponse = await axios.post( process.env.SNOWFLAKE_INFERENCE_ENDPOINT, { prompt: `Explain this data in plain English: ${JSON.stringify(dataframe)}`, }, { headers: { Authorization: `Bearer ${token}` } }, ); return ( inferenceResponse.data.choices[0]?.message?.content || "No explanation found." ); } return firstChoice || "No response found."; } ``` ### Send a Message Back to Zoom Chat ```javascript async function sendZoomMessage(toJid, message) { const base64Credentials = Buffer.from( `${process.env.ZOOM_CLIENT_ID}:${process.env.ZOOM_CLIENT_SECRET}`, ).toString("base64"); const tokenResponse = await axios.post( "https://zoom.us/oauth/token?grant_type=client_credentials", {}, { headers: { Authorization: `Basic ${base64Credentials}`, }, }, ); const accessToken = tokenResponse.data.access_token; await axios.post( "https://api.zoom.us/v2/im/chat/messages", { robot_jid: process.env.ZOOM_BOT_JID, to_jid: toJid, account_id: process.env.ZOOM_ACCOUNT_ID, content: { body: [{ type: "message", text: message }], }, }, { headers: { Authorization: `Bearer ${accessToken}` } }, ); } ``` ### Start the Express Server ```javascript const PORT = process.env.PORT || 5000; app.listen(PORT, () => console.log(`Server running on ${PORT}`)); ``` ## Run It 1. Start ngrok: ```bash ngrok http 5000 ``` 2. Fire up your Node app: ```bash node index.js ``` ## Try It Out Send a message inside Zoom Team Chat: - "Show me a breakdown of support tickets by category" - "What are the payment terms for Snowtires?" You should get fast, structured or unstructured, AI-generated responses directly in Zoom Team Chat. ![Zoom Teams Chat Bot in Action - Example of querying support tickets breakdown](/img/blog/ojussave/zoom-snowflake/dash-cortex-agents-zoom.gif) Happy Coding!