Skip to main content
RapidDev - Software Development Agency
replit-integrationsStandard API Integration

How to Integrate Replit with Zoom

To integrate Replit with Zoom, create a Server-to-Server OAuth app in the Zoom Marketplace, store your credentials in Replit Secrets (lock icon πŸ”’), generate an access token, and use the Zoom REST API to create and manage meetings from your Node.js or Python server. Deploy on Reserved VM for reliable webhook event processing.

What you'll learn

  • How to create a Zoom Server-to-Server OAuth app in the Zoom Marketplace
  • How to store Zoom credentials in Replit Secrets and generate access tokens
  • How to create and manage Zoom meetings via the REST API
  • How to receive and verify Zoom webhook events in a deployed Replit app
  • Which Replit deployment type to use for reliable Zoom webhook processing
Book a free consultation
4.9Clutch rating ⭐
600+Happy partners
17+Countries served
190+Team members
Intermediate15 min read30 minutesCommunicationMarch 2026RapidDev Engineering Team
TL;DR

To integrate Replit with Zoom, create a Server-to-Server OAuth app in the Zoom Marketplace, store your credentials in Replit Secrets (lock icon πŸ”’), generate an access token, and use the Zoom REST API to create and manage meetings from your Node.js or Python server. Deploy on Reserved VM for reliable webhook event processing.

Why Integrate Zoom with Replit?

Automating Zoom meeting creation and management is a common need for scheduling apps, education platforms, telemedicine tools, and any product where video calls are a core feature. The Zoom REST API lets you create meetings programmatically, generate join links for specific users, manage webinars, retrieve meeting recordings, and track participant activity β€” all from your Replit backend.

Zoom offers several authentication methods, but Server-to-Server OAuth is the most appropriate for Replit apps that need to manage meetings on behalf of a Zoom account. Unlike legacy JWT apps (deprecated by Zoom in 2023) or user-OAuth flows that require browser redirects, Server-to-Server OAuth uses client credentials to generate short-lived access tokens server-side. This is clean, secure, and well-suited to a backend API that creates meetings automatically without user interaction.

Webhook events are where the real automation value lies. Zoom can notify your Replit app when meetings start and end, when participants join or leave, when recordings become available, and when meetings are updated. Your app can react to these events in real time β€” sending notifications, updating database records, triggering billing, or kicking off post-meeting workflows. For reliable webhook reception, Replit's Reserved VM deployment ensures your app is always running to capture every event.

Integration method

Standard API Integration

Replit connects to Zoom via the Zoom REST API using OAuth 2.0 authentication. A Server-to-Server OAuth app in the Zoom Marketplace provides credentials that your Replit backend uses to generate access tokens and make API calls. For real-time meeting events, Zoom sends webhook POST requests to your deployed Replit URL, which must be accessible 24/7.

Prerequisites

  • A Zoom account with admin permissions (Pro plan or higher to access the Marketplace and create Server-to-Server OAuth apps)
  • A Replit account with a Node.js or Python Repl created
  • Basic understanding of OAuth 2.0 and REST APIs
  • Node.js (Express) or Python (Flask/requests) for the server framework

Step-by-step guide

1

Create a Zoom Server-to-Server OAuth app

Go to marketplace.zoom.us and log in with your Zoom admin account. Click 'Develop' in the top menu, then 'Build App'. Choose 'Server-to-Server OAuth' as the app type β€” this is the correct choice for backend integrations that don't require user login flows. Give your app a name like 'Replit Meeting Manager'. On the app information page, add a short description and your company name. Navigate to the 'Scopes' tab and add the specific API permissions your app needs. For basic meeting management, add: meeting:write:admin (create/update/delete meetings), meeting:read:admin (list and read meeting details). For webinars, add: webinar:write:admin and webinar:read:admin. For recording access, add: recording:read:admin. Keep scopes minimal β€” only add what your app actually needs. After adding scopes, click 'Activate your app'. Zoom will display your Account ID, Client ID, and Client Secret. Copy all three and store them in Replit Secrets in the next step. Note: JWT app types were deprecated by Zoom in June 2023, so Server-to-Server OAuth is now the standard approach for server-side integrations.

Pro tip: The app activation step is easy to miss. After adding scopes, click the 'Activate your app' button on the App Credentials tab β€” the app won't work until activated.

Expected result: A Zoom Server-to-Server OAuth app is created and activated with the required scopes. You have the Account ID, Client ID, and Client Secret ready to store.

2

Store Zoom credentials in Replit Secrets

Open your Repl and click the lock icon (πŸ”’) in the left sidebar to open the Secrets pane. Add three secrets: ZOOM_ACCOUNT_ID (your Account ID from the app credentials page), ZOOM_CLIENT_ID (your Client ID), and ZOOM_CLIENT_SECRET (your Client Secret). These three values together allow your server to generate OAuth access tokens. The Client Secret is particularly sensitive β€” if exposed, anyone could create or access meetings in your Zoom account. Replit Secrets store these values AES-256 encrypted and inject them as environment variables at runtime. They're never stored in your code files, Git history, or visible to other users who view your Repl. If your Client Secret is compromised, regenerate it immediately in the Zoom Marketplace app settings and update the Replit Secret.

check_secrets.py
1# Python β€” verify Zoom credentials are set
2import os
3
4required = ["ZOOM_ACCOUNT_ID", "ZOOM_CLIENT_ID", "ZOOM_CLIENT_SECRET"]
5for key in required:
6 val = os.environ.get(key)
7 if not val:
8 raise EnvironmentError(f"{key} not found. Add it in Replit Secrets (lock icon).")
9 print(f"{key}: loaded")
10print("All Zoom credentials verified.")

Expected result: All three Zoom secrets appear in Replit Secrets and the verification script confirms they are all loaded.

3

Implement OAuth token generation

Server-to-Server OAuth works by exchanging your Account ID, Client ID, and Client Secret for a short-lived access token (valid for 1 hour). Your code calls Zoom's token endpoint at https://zoom.us/oauth/token with a grant_type of account_credentials. The Client ID and Client Secret are sent as HTTP Basic auth credentials. The token endpoint returns an access_token that you include as a Bearer token in all subsequent Zoom API calls. Since tokens expire after one hour, you need a token management strategy. The simplest approach for a Replit app is to fetch a new token on each request if the current token is older than 55 minutes. For high-volume apps, cache the token in memory and refresh it proactively before expiry. Do not fetch a new token for every single API call β€” that wastes time and hits rate limits on the token endpoint.

zoom-auth.js
1// Node.js β€” Zoom OAuth token manager (zoom-auth.js)
2const ZOOM_ACCOUNT_ID = process.env.ZOOM_ACCOUNT_ID;
3const ZOOM_CLIENT_ID = process.env.ZOOM_CLIENT_ID;
4const ZOOM_CLIENT_SECRET = process.env.ZOOM_CLIENT_SECRET;
5
6let tokenCache = { token: null, expiresAt: 0 };
7
8async function getZoomToken() {
9 const now = Date.now();
10 // Return cached token if it has more than 5 minutes remaining
11 if (tokenCache.token && tokenCache.expiresAt > now + 5 * 60 * 1000) {
12 return tokenCache.token;
13 }
14
15 const credentials = Buffer.from(`${ZOOM_CLIENT_ID}:${ZOOM_CLIENT_SECRET}`).toString('base64');
16
17 const response = await fetch(
18 `https://zoom.us/oauth/token?grant_type=account_credentials&account_id=${ZOOM_ACCOUNT_ID}`,
19 {
20 method: 'POST',
21 headers: {
22 'Authorization': `Basic ${credentials}`,
23 'Content-Type': 'application/x-www-form-urlencoded'
24 }
25 }
26 );
27
28 if (!response.ok) {
29 const error = await response.json();
30 throw new Error(`Failed to get Zoom token: ${JSON.stringify(error)}`);
31 }
32
33 const data = await response.json();
34 tokenCache = {
35 token: data.access_token,
36 expiresAt: now + (data.expires_in * 1000)
37 };
38
39 return tokenCache.token;
40}
41
42module.exports = { getZoomToken };

Pro tip: Token expiry errors (401 responses from Zoom after 1 hour) mean your token cache isn't working. Always check expiry with a 5-minute buffer β€” Zoom tokens can be invalidated slightly before the official expiry time.

Expected result: getZoomToken() returns a valid access token on first call and returns the cached token on subsequent calls within the same hour.

4

Create and manage Zoom meetings

With token generation working, you can now call the Zoom Meetings API. The primary endpoint for creating meetings is POST /v2/users/{userId}/meetings where userId is either a user's email address registered in your Zoom account or 'me' for the account owner. The meeting object accepts a topic, type (1 for instant, 2 for scheduled), start_time in ISO 8601 UTC format, duration in minutes, timezone, agenda, and settings like password, waiting room, and auto recording. The API response includes the meeting id, join_url (for participants), start_url (for the host), and password. Store the meeting id in your database so you can retrieve or update the meeting later. For updating meetings, use PATCH /v2/meetings/{meetingId}. For deleting, use DELETE /v2/meetings/{meetingId}. The Zoom API uses meeting IDs as integers, not strings β€” be careful with type handling when storing them in databases.

zoom-api.js
1// Node.js β€” Zoom meeting operations (zoom-api.js)
2const { getZoomToken } = require('./zoom-auth');
3
4const ZOOM_BASE = 'https://api.zoom.us/v2';
5
6async function zoomRequest(method, path, body = null) {
7 const token = await getZoomToken();
8 const options = {
9 method,
10 headers: {
11 'Authorization': `Bearer ${token}`,
12 'Content-Type': 'application/json'
13 }
14 };
15 if (body) options.body = JSON.stringify(body);
16
17 const response = await fetch(`${ZOOM_BASE}${path}`, options);
18 if (!response.ok) {
19 const error = await response.json();
20 throw new Error(`Zoom API ${response.status}: ${JSON.stringify(error)}`);
21 }
22 if (response.status === 204) return null; // No content
23 return response.json();
24}
25
26async function createMeeting({ userId = 'me', topic, startTime, durationMinutes, agenda = '' }) {
27 return zoomRequest('POST', `/users/${userId}/meetings`, {
28 topic,
29 type: 2, // Scheduled meeting
30 start_time: startTime, // ISO 8601, e.g. '2026-04-15T14:00:00Z'
31 duration: durationMinutes,
32 agenda,
33 settings: {
34 host_video: true,
35 participant_video: true,
36 waiting_room: true,
37 auto_recording: 'none'
38 }
39 });
40}
41
42async function getMeeting(meetingId) {
43 return zoomRequest('GET', `/meetings/${meetingId}`);
44}
45
46async function deleteMeeting(meetingId) {
47 return zoomRequest('DELETE', `/meetings/${meetingId}`);
48}
49
50module.exports = { createMeeting, getMeeting, deleteMeeting, zoomRequest };

Expected result: createMeeting() returns a meeting object with id, join_url, start_url, and password fields. getMeeting() retrieves meeting details by ID.

5

Receive Zoom webhook events

Zoom webhooks provide real-time notifications about meeting events. To configure webhooks, return to your Server-to-Server OAuth app in the Zoom Marketplace, go to the 'Feature' tab, and enable Event Subscriptions. Add a subscription, enter your deployed Replit URL plus a webhook path (e.g., https://your-app.replit.app/zoom-webhook), and select the event types you want: meeting.started, meeting.ended, meeting.participant_joined, meeting.participant_left, and recording.completed are the most commonly needed. Zoom requires a URL validation step β€” when you add the webhook URL, Zoom sends a validation POST request with a plainToken field. Your server must respond immediately with an encrypted token to prove you control the URL. The encryption uses HMAC-SHA256 with your webhook secret token (found in the Event Subscriptions settings). For production reliability, use Reserved VM deployment β€” Zoom has shorter retry windows than some other webhook providers, and missing events due to cold starts can cause data inconsistencies.

webhook.js
1// Node.js β€” Zoom webhook handler (webhook.js)
2const crypto = require('crypto');
3
4const ZOOM_WEBHOOK_SECRET = process.env.ZOOM_WEBHOOK_SECRET;
5
6app.post('/zoom-webhook', express.json(), (req, res) => {
7 const { event, payload } = req.body;
8
9 // Zoom URL validation challenge (required when setting up webhook)
10 if (event === 'endpoint.url_validation') {
11 const { plainToken } = payload;
12 const encryptedToken = crypto
13 .createHmac('sha256', ZOOM_WEBHOOK_SECRET)
14 .update(plainToken)
15 .digest('hex');
16 return res.json({
17 plainToken,
18 encryptedToken
19 });
20 }
21
22 // Verify webhook signature for security
23 const message = `v0:${req.headers['x-zm-request-timestamp']}:${JSON.stringify(req.body)}`;
24 const expectedHash = 'v0=' + crypto
25 .createHmac('sha256', ZOOM_WEBHOOK_SECRET)
26 .update(message)
27 .digest('hex');
28
29 if (req.headers['x-zm-signature'] !== expectedHash) {
30 return res.status(401).json({ error: 'Invalid webhook signature' });
31 }
32
33 // Handle specific events
34 console.log(`Zoom event: ${event}`);
35 switch (event) {
36 case 'meeting.started':
37 console.log(`Meeting started: ${payload.object.id}`);
38 break;
39 case 'meeting.ended':
40 console.log(`Meeting ended: ${payload.object.id}, duration: ${payload.object.duration}`);
41 break;
42 case 'meeting.participant_joined':
43 console.log(`Participant joined: ${payload.object.participant?.user_name}`);
44 break;
45 case 'recording.completed':
46 console.log(`Recording ready for meeting: ${payload.object.id}`);
47 break;
48 }
49
50 res.json({ status: 'ok' });
51});

Pro tip: Add ZOOM_WEBHOOK_SECRET to Replit Secrets. It's the 'Secret Token' from your Event Subscriptions settings in the Zoom Marketplace app β€” different from your Client Secret.

Expected result: Zoom validates your webhook URL successfully. Meeting events arrive as POST requests and your server logs the event type and meeting ID.

6

Python alternative: Flask Zoom integration

The Python implementation uses the requests library with Bearer token authentication. The token management logic is the same β€” exchange credentials for a short-lived token, cache it, and refresh before expiry. Python's time.time() returns Unix timestamps in seconds, making expiry calculation straightforward. The requests.Session class lets you set default headers once and reuse the session for multiple API calls, which is cleaner than setting headers on every individual request. For webhook signature verification in Flask, use Python's hmac and hashlib modules. Flask provides request.get_json() for the webhook body and request.headers for the signature headers. Ensure you're reading the raw timestamp from the x-zm-request-timestamp header consistently β€” this value is used in the signature calculation.

app.py
1# Python β€” Flask Zoom integration (app.py)
2import os
3import time
4import hmac
5import hashlib
6import requests
7from flask import Flask, request, jsonify
8
9app = Flask(__name__)
10
11ZOOM_ACCOUNT_ID = os.environ["ZOOM_ACCOUNT_ID"]
12ZOOM_CLIENT_ID = os.environ["ZOOM_CLIENT_ID"]
13ZOOM_CLIENT_SECRET = os.environ["ZOOM_CLIENT_SECRET"]
14ZOOM_WEBHOOK_SECRET = os.environ.get("ZOOM_WEBHOOK_SECRET", "")
15ZOOM_BASE = "https://api.zoom.us/v2"
16
17_token_cache = {"token": None, "expires_at": 0}
18
19def get_zoom_token():
20 if _token_cache["token"] and _token_cache["expires_at"] > time.time() + 300:
21 return _token_cache["token"]
22 resp = requests.post(
23 f"https://zoom.us/oauth/token?grant_type=account_credentials&account_id={ZOOM_ACCOUNT_ID}",
24 auth=(ZOOM_CLIENT_ID, ZOOM_CLIENT_SECRET)
25 )
26 resp.raise_for_status()
27 data = resp.json()
28 _token_cache["token"] = data["access_token"]
29 _token_cache["expires_at"] = time.time() + data["expires_in"]
30 return _token_cache["token"]
31
32@app.route("/meetings", methods=["POST"])
33def create_meeting():
34 data = request.get_json()
35 token = get_zoom_token()
36 headers = {"Authorization": f"Bearer {token}", "Content-Type": "application/json"}
37 resp = requests.post(f"{ZOOM_BASE}/users/me/meetings",
38 json={
39 "topic": data["topic"],
40 "type": 2,
41 "start_time": data["start_time"],
42 "duration": data["duration"],
43 "settings": {"waiting_room": True}
44 },
45 headers=headers, timeout=10)
46 resp.raise_for_status()
47 meeting = resp.json()
48 return jsonify({"id": meeting["id"], "join_url": meeting["join_url"]})
49
50@app.route("/zoom-webhook", methods=["POST"])
51def zoom_webhook():
52 body = request.get_json()
53 event = body.get("event")
54 payload = body.get("payload", {})
55
56 if event == "endpoint.url_validation":
57 plain_token = payload["plainToken"]
58 encrypted = hmac.new(ZOOM_WEBHOOK_SECRET.encode(),
59 plain_token.encode(), hashlib.sha256).hexdigest()
60 return jsonify({"plainToken": plain_token, "encryptedToken": encrypted})
61
62 print(f"Zoom event: {event}")
63 return jsonify({"status": "ok"})
64
65if __name__ == "__main__":
66 app.run(host="0.0.0.0", port=3000)

Expected result: Flask app creates Zoom meetings via POST /meetings and responds correctly to Zoom's webhook URL validation challenge.

Common use cases

Automated Meeting Scheduler

When a user books an appointment in your app, automatically create a Zoom meeting for the scheduled time, send the join link to both participants via email or notification, and save the meeting ID to your database. Cancel or update the meeting if the appointment changes.

Replit Prompt

Build an Express API that accepts appointment booking details (host email, start time, duration, topic), creates a Zoom meeting via the Zoom API, stores the meeting ID and join URL in a database, and returns the meeting details to the caller.

Copy this prompt to try it in Replit

Webinar Registration and Attendance Tracking

Create Zoom webinars programmatically for events, register attendees via your own registration form rather than Zoom's, and track attendance by listening to participant joined/left webhook events to update attendance records in your database.

Replit Prompt

Build a Flask app that creates Zoom webinars from event data, registers attendees using the Zoom Webinar Registration API, and receives participant webhook events to track attendance. Store attendance records in a PostgreSQL database.

Copy this prompt to try it in Replit

Meeting Recording Processor

When a Zoom meeting ends and a cloud recording becomes available, Zoom sends a webhook event with the recording download URL. Your Replit app receives this event, downloads the recording transcript or video, and processes it β€” generating a summary, storing it, or sending it to participants.

Replit Prompt

Build a webhook receiver in Node.js that listens for Zoom recording.completed events, extracts the download URL and meeting topic from the payload, and triggers a processing job that fetches the transcript and saves it to your database.

Copy this prompt to try it in Replit

Troubleshooting

401 Invalid access token when calling Zoom API

Cause: The access token has expired (tokens last only 1 hour), the token cache is not working correctly, or the Client ID/Secret credentials are wrong.

Solution: Verify ZOOM_CLIENT_ID and ZOOM_CLIENT_SECRET in Replit Secrets match exactly what's shown in the Zoom Marketplace app. Check that your token cache logic correctly computes expiry with a buffer margin. Ensure the app is activated in the Zoom Marketplace β€” inactive apps return 401 even with correct credentials.

typescript
1// Test token generation directly
2const { getZoomToken } = require('./zoom-auth');
3getZoomToken().then(t => console.log('Token:', t.substring(0, 20) + '...')).catch(console.error);

Zoom webhook URL validation fails during setup

Cause: Your endpoint is not responding to the endpoint.url_validation event correctly, or your deployed URL is not accessible from Zoom's servers.

Solution: Ensure your deployed Replit app is running (use the Deployments tab to check status) before registering the webhook URL. The validation response must include both plainToken and encryptedToken fields. Verify ZOOM_WEBHOOK_SECRET is set in Replit Secrets and matches the 'Secret Token' in Zoom's Event Subscriptions settings.

typescript
1// Minimal URL validation handler
2if (req.body.event === 'endpoint.url_validation') {
3 const plain = req.body.payload.plainToken;
4 const enc = crypto.createHmac('sha256', process.env.ZOOM_WEBHOOK_SECRET)
5 .update(plain).digest('hex');
6 return res.json({ plainToken: plain, encryptedToken: enc });
7}

404 User not found when creating meetings for a specific user email

Cause: The user email is not in your Zoom account, or your Server-to-Server OAuth app doesn't have admin-level scopes to manage other users' meetings.

Solution: Use 'me' as the userId to create meetings for the account owner, which requires no additional permissions. To create meetings for other users, ensure they have Zoom accounts under your organization and your app has the meeting:write:admin scope (note the :admin suffix β€” not just meeting:write).

typescript
1// Use 'me' for account owner, or verified user email
2const userId = 'me'; // or 'user@yourcompany.com'
3const path = `/users/${userId}/meetings`;

Webhook events are being missed or arriving late

Cause: The Replit app is deployed on Autoscale and is experiencing cold starts when Zoom sends events, or the app is in development mode and sleeping.

Solution: Switch to Reserved VM deployment for the webhook receiver to ensure zero cold-start latency. Reserved VM keeps your app running continuously, while Autoscale can take 1-3 seconds to cold-start. Zoom retries failed webhook deliveries, but only a limited number of times β€” for recording.completed and billing-critical events, the guaranteed uptime of Reserved VM is worth the added cost.

Best practices

  • Store ZOOM_ACCOUNT_ID, ZOOM_CLIENT_ID, and ZOOM_CLIENT_SECRET in Replit Secrets (lock icon πŸ”’) β€” never hardcode them
  • Cache access tokens for up to 55 minutes rather than fetching a new token on every API call β€” the token endpoint has strict rate limits
  • Use Server-to-Server OAuth instead of legacy JWT apps β€” Zoom deprecated JWT in June 2023 and will not support new JWT integrations
  • Request only the minimum Zoom scopes your app needs β€” excess scopes increase your attack surface if credentials are compromised
  • Always respond to Zoom's endpoint.url_validation challenge immediately when setting up webhooks β€” without this, webhook registration will fail
  • Use Reserved VM deployment for webhook receivers handling meeting events β€” cold starts on Autoscale can cause missed events if Zoom's retry window is short
  • Store the Zoom meeting ID (not just the join_url) in your database β€” you need the ID to update, delete, or retrieve meeting details later
  • Verify webhook signatures using HMAC-SHA256 with your webhook secret token to prevent spoofed event injection

Alternatives

Frequently asked questions

How do I connect Replit to Zoom?

Create a Server-to-Server OAuth app in the Zoom Marketplace (marketplace.zoom.us). Copy the Account ID, Client ID, and Client Secret, then store them in Replit Secrets (lock icon πŸ”’). Your server exchanges these credentials for an access token and uses it as a Bearer token in all Zoom API calls.

Does Replit work with Zoom for free?

Creating a Server-to-Server OAuth app requires a Zoom Pro plan or higher. The Zoom free plan does not include Marketplace access for creating API apps. Replit's free tier can run the integration in development. For production with reliable webhook reception, a paid Replit deployment is recommended.

Why are Zoom API calls returning 401 errors?

Zoom access tokens expire after 1 hour. If your token caching isn't refreshing before expiry, calls will fail with 401. Also verify your app is activated in the Zoom Marketplace β€” inactive apps return 401 regardless of credentials. Check that your Client ID and Client Secret in Replit Secrets match exactly what's shown in the Marketplace app settings.

Can Replit receive Zoom webhook events?

Yes, but you must use a deployed Replit URL (ending in .replit.app or a custom domain), not the development editor URL. Go to your Server-to-Server OAuth app in Zoom Marketplace, enable Event Subscriptions, and enter your deployment URL. Zoom requires your endpoint to pass a URL validation challenge before events are sent.

What Zoom scopes do I need for creating meetings?

For creating and managing meetings server-side, add meeting:write:admin and meeting:read:admin scopes to your Server-to-Server OAuth app. The :admin suffix is required for Server-to-Server apps. For webinars, add webinar:write:admin. For recording access, add recording:read:admin.

RapidDev

Talk to an Expert

Our team has built 600+ apps. Get personalized help with your project.

Book a free consultation

Need help with your project?

Our experts have built 600+ apps and can accelerate your development. Book a free consultation β€” no strings attached.

Book a free consultation

We put the rapid in RapidDev

Need a dedicated strategic tech and growth partner? Discover what RapidDev can do for your business! Book a call with our team to schedule a free, no-obligation consultation. We'll discuss your project and provide a custom quote at no cost.