To integrate Zoom with V0 by Vercel, create a Next.js API route that calls the Zoom REST API using OAuth 2.0 Server-to-Server credentials stored as Vercel environment variables. V0 generates the meeting management UI; the API route creates and manages Zoom meetings server-side so your API credentials never reach the browser.
Manage Zoom Meetings Programmatically in Your V0 Next.js App
Zoom's REST API lets you create and manage meetings, retrieve recordings, send notifications, and build custom scheduling workflows — all from your own application. For founders building productivity tools, client portals, tutoring platforms, or event management apps with V0, Zoom integration means you can create meeting links automatically when users book appointments, send calendar invites, and track attendance without users having to copy-paste Zoom links manually.
The Zoom API uses OAuth 2.0 for authentication. For server-to-server integrations (where your app acts on behalf of a Zoom account rather than individual users), Zoom recommends the Server-to-Server OAuth app type. This generates a Client ID and Client Secret that you exchange for an access token with each API call. The access token expires after one hour, so your API route needs to handle token refresh.
V0 generates the UI for your meeting scheduler — the form to create a new meeting, the list of upcoming meetings, and the join button. The Next.js API route handles all the Zoom API communication securely. This guide walks through the Server-to-Server OAuth flow, which is the most common integration pattern for SaaS apps managing Zoom meetings on behalf of their users.
Integration method
V0 generates the meeting scheduling and management UI in Next.js. A server-side API route at app/api/zoom/route.ts handles OAuth 2.0 token exchange with Zoom and proxies all Zoom REST API calls, keeping your Client ID and Client Secret secure in Vercel environment variables. The frontend communicates only with your own API route, never directly with Zoom.
Prerequisites
- A Zoom account (Pro plan or higher recommended for API access to all features)
- A Zoom Server-to-Server OAuth app created at marketplace.zoom.us with Client ID, Client Secret, and Account ID
- The app must have the required scopes: meeting:write:admin, meeting:read:admin (at minimum)
- A V0 account at v0.dev and a Vercel account for deployment
- Basic familiarity with OAuth 2.0 and HTTP API patterns
Step-by-step guide
Create a Zoom Server-to-Server OAuth App
Create a Zoom Server-to-Server OAuth App
Before writing any code, you need to create the Zoom app that will authorize your Next.js application to use the Zoom API. Go to marketplace.zoom.us and sign in with your Zoom account. Click 'Develop' in the top navigation, then 'Build App'. From the app types, select 'Server-to-Server OAuth' — this type is designed for backend integrations where your server acts on behalf of your Zoom account without needing individual users to authorize. Give your app a name like 'My Scheduling App'. After creation, Zoom will generate three values you need to save: Account ID, Client ID, and Client Secret. Copy all three immediately and store them securely — the Client Secret is only shown once in some Zoom versions. Next, configure the required API scopes. Click 'Scopes' in the left sidebar and add: meeting:write:admin (to create and delete meetings), meeting:read:admin (to list meetings), and user:read:admin (to get user info). Save the scopes. Finally, activate the app by clicking 'Activate your app' — the app must be active for the OAuth token endpoint to work. Note that Server-to-Server OAuth apps act on behalf of your Zoom account, not individual user accounts.
Pro tip: Write down your Account ID, Client ID, and Client Secret in a password manager immediately. The Client Secret may not be retrievable after you leave the page — you would need to regenerate it.
Expected result: Your Zoom Server-to-Server OAuth app is created and activated with the required scopes. You have Account ID, Client ID, and Client Secret values ready.
Generate the Meeting UI with V0
Generate the Meeting UI with V0
Open V0 at v0.dev and describe the meeting management interface you want to build. V0 works best when you describe the full data flow — mention that meeting creation should POST to /api/zoom/meetings and the meetings list should GET from /api/zoom/meetings. V0 will generate a React component with the form and list, plus a stub API route. The meeting creation form should collect the topic, start time, duration, timezone, and optionally a password and waiting room setting. The meetings list should display each meeting's topic, start time, join URL, and a delete button. V0 can also generate a copy-to-clipboard button for join URLs — ask for it explicitly in your prompt since it is a common UX pattern for meeting links. Once the design looks right, deploy from V0 to get a Vercel preview URL. You will configure real Zoom credentials in the next steps.
Create a Zoom meeting scheduler with two sections: a 'New Meeting' form (fields: topic, date, time, duration in minutes, timezone dropdown, optional password) and a 'Upcoming Meetings' table (columns: topic, start time, duration, join URL with copy button, delete button). The form POSTs to /api/zoom/meetings and the table GETs from /api/zoom/meetings. Show a loading state on both.
Paste this in V0 chat
Pro tip: Include a timezone selector in the meeting creation form — Zoom requires timezone to be specified for scheduled meetings, and defaulting to the user's browser timezone (Intl.DateTimeFormat().resolvedOptions().timeZone) prevents scheduling errors.
Expected result: V0 generates a meeting scheduler UI with form and table. The project is deployed to a Vercel preview URL showing placeholder data.
Build the Zoom API Route with OAuth Token Exchange
Build the Zoom API Route with OAuth Token Exchange
The core of the integration is the Next.js API route that handles Zoom API calls. Zoom's Server-to-Server OAuth requires exchanging your Account ID, Client ID, and Client Secret for a bearer access token at https://zoom.us/oauth/token. This token expires after one hour. For simplicity in a serverless context, request a new token with each API call rather than managing token refresh state — the token endpoint is fast and the overhead is minimal in a Vercel serverless function. Create app/api/zoom/meetings/route.ts with GET (list meetings) and POST (create meeting) handlers. The GET handler fetches the host user's ID first via the /users/me endpoint, then uses it to list meetings. The POST handler creates a scheduled meeting with the data from the request body. Both handlers call a shared getAccessToken() helper function that performs the OAuth token exchange. Note that the Authorization header for the token exchange uses Basic auth with base64-encoded credentials: btoa(clientId:clientSecret).
1import { NextResponse } from 'next/server';23async function getAccessToken(): Promise<string> {4 const credentials = Buffer.from(5 `${process.env.ZOOM_CLIENT_ID}:${process.env.ZOOM_CLIENT_SECRET}`6 ).toString('base64');78 const response = await fetch(9 `https://zoom.us/oauth/token?grant_type=account_credentials&account_id=${process.env.ZOOM_ACCOUNT_ID}`,10 {11 method: 'POST',12 headers: {13 Authorization: `Basic ${credentials}`,14 'Content-Type': 'application/x-www-form-urlencoded',15 },16 }17 );1819 const data = await response.json();20 if (!data.access_token) {21 throw new Error('Failed to get Zoom access token');22 }23 return data.access_token;24}2526export async function GET() {27 try {28 const token = await getAccessToken();29 const meRes = await fetch('https://api.zoom.us/v2/users/me', {30 headers: { Authorization: `Bearer ${token}` },31 });32 const me = await meRes.json();3334 const meetingsRes = await fetch(35 `https://api.zoom.us/v2/users/${me.id}/meetings?type=scheduled`,36 { headers: { Authorization: `Bearer ${token}` } }37 );38 const meetings = await meetingsRes.json();39 return NextResponse.json({ meetings: meetings.meetings ?? [] });40 } catch (error) {41 console.error('Zoom GET error:', error);42 return NextResponse.json({ error: 'Failed to fetch meetings' }, { status: 500 });43 }44}4546export async function POST(request: Request) {47 try {48 const body = await request.json();49 const token = await getAccessToken();50 const meRes = await fetch('https://api.zoom.us/v2/users/me', {51 headers: { Authorization: `Bearer ${token}` },52 });53 const me = await meRes.json();5455 const response = await fetch(`https://api.zoom.us/v2/users/${me.id}/meetings`, {56 method: 'POST',57 headers: {58 Authorization: `Bearer ${token}`,59 'Content-Type': 'application/json',60 },61 body: JSON.stringify({62 topic: body.topic,63 type: 2,64 start_time: body.startTime,65 duration: body.duration,66 timezone: body.timezone,67 password: body.password ?? '',68 settings: { waiting_room: true, host_video: true, participant_video: true },69 }),70 });7172 const meeting = await response.json();73 if (!response.ok) {74 return NextResponse.json({ error: meeting.message }, { status: response.status });75 }76 return NextResponse.json({ meeting });77 } catch (error) {78 console.error('Zoom POST error:', error);79 return NextResponse.json({ error: 'Failed to create meeting' }, { status: 500 });80 }81}Pro tip: Cache the access token in a module-level variable with an expiry check to avoid making a token exchange request on every API call. This reduces latency for high-traffic deployments.
Expected result: The API route handles GET and POST requests. Testing GET at /api/zoom/meetings returns the meetings array from your Zoom account.
Add Environment Variables in Vercel Dashboard
Add Environment Variables in Vercel Dashboard
Configure your Zoom OAuth credentials in Vercel so the deployed API route can authenticate. Navigate to your project in the Vercel dashboard, go to Settings → Environment Variables, and add three variables. ZOOM_ACCOUNT_ID is the Account ID from your Zoom marketplace app (a string like AbCdEfGhIjKlMnOp). ZOOM_CLIENT_ID is the Client ID shown in your app's credentials page. ZOOM_CLIENT_SECRET is the Client Secret — treat this like a password and never commit it to Git. Set all three for the Production and Preview environments. If you want the Preview deployment to use a separate Zoom app for safety (so test meeting creation does not clutter your real Zoom account), create a second Zoom Server-to-Server OAuth app for development and use its credentials for the Preview environment scope. After adding all variables, go to the Deployments tab and click Redeploy to apply the new configuration. If you access the Vercel environment variables section and see the variables are grayed out after redeploying, the deployment has successfully picked them up — the values are masked for security.
1# .env.local (local development only — set in Vercel Dashboard for deployments)2ZOOM_ACCOUNT_ID=your_account_id_here3ZOOM_CLIENT_ID=your_client_id_here4ZOOM_CLIENT_SECRET=your_client_secret_herePro tip: Do not prefix these variables with NEXT_PUBLIC_ — they must remain server-only. Adding NEXT_PUBLIC_ would expose your Zoom credentials in the browser JavaScript bundle.
Expected result: All three environment variables are set in Vercel Dashboard. The latest deployment is redeployed and the API route can now successfully exchange credentials for Zoom access tokens.
Test the Integration and Handle Meeting Webhooks
Test the Integration and Handle Meeting Webhooks
Test the full integration by visiting your Vercel deployment URL and creating a meeting through the form. If creation succeeds, the meeting should appear in the table within seconds. Common failures at this stage include the Zoom app not being 'activated' (check the app status in Zoom Marketplace) or missing scopes (the error message will indicate which scope is missing). To handle Zoom webhooks — for example, receiving a notification when a meeting starts or ends — create an additional route at app/api/zoom/webhook/route.ts. Zoom webhooks require URL verification: when you register a webhook URL in the Zoom Marketplace app settings, Zoom sends a CRC validation request that your endpoint must respond to correctly. The validation involves computing an HMAC-SHA256 hash of the challenge token using your Webhook Secret Token. Once verified, Zoom will send event notifications to your endpoint for the events you subscribe to (meeting.started, meeting.ended, participant.joined, etc.). For RapidDev clients building complex scheduling platforms, we can help implement full webhook handling and database sync for meeting attendance tracking.
Add a success notification that appears after a meeting is created, showing the meeting topic and a copy button for the join URL. When the 'Delete' button is clicked in the meetings table, send a DELETE request to /api/zoom/meetings/[id] and remove the row optimistically from the UI.
Paste this in V0 chat
1// app/api/zoom/webhook/route.ts2import { NextResponse } from 'next/server';3import crypto from 'crypto';45export async function POST(request: Request) {6 const body = await request.json();78 // Handle Zoom URL validation challenge9 if (body.event === 'endpoint.url_validation') {10 const hash = crypto11 .createHmac('sha256', process.env.ZOOM_WEBHOOK_SECRET!)12 .update(body.payload.plainToken)13 .digest('hex');14 return NextResponse.json({15 plainToken: body.payload.plainToken,16 encryptedToken: hash,17 });18 }1920 // Handle meeting events21 if (body.event === 'meeting.started') {22 console.log('Meeting started:', body.payload.object.id);23 }24 if (body.event === 'meeting.ended') {25 console.log('Meeting ended:', body.payload.object.id);26 }2728 return NextResponse.json({ received: true });29}Pro tip: Add ZOOM_WEBHOOK_SECRET_TOKEN to your Vercel environment variables when setting up webhooks. This is a separate secret from your OAuth Client Secret — find it in the Zoom Marketplace app's Feature section.
Expected result: Meeting creation and listing work end-to-end in the Vercel deployment. The webhook endpoint passes Zoom's URL validation challenge and receives meeting event notifications.
Common use cases
Automated Meeting Scheduler
When a user books an appointment in your app, automatically create a Zoom meeting and return the join URL. Combine with Calendly or your own booking form to create end-to-end scheduling that provisions the Zoom link without any manual steps.
Create a meeting scheduler form with fields for meeting topic, date picker, time picker, and duration dropdown (30min/60min/90min). Add a 'Schedule Meeting' button that POSTs to /api/zoom/meetings and displays the generated join link and passcode in a success card.
Copy this prompt to try it in V0
Meeting Management Dashboard
Build an internal dashboard that lists all upcoming scheduled Zoom meetings, shows participant counts, and lets admins start or delete meetings. Useful for operations teams managing webinars or client calls at scale.
Build a meeting management dashboard with a table of upcoming Zoom meetings showing topic, host, scheduled time, and number of registrants. Include 'Start Meeting' and 'Delete' action buttons per row. Fetch from /api/zoom/meetings with a GET request on page load.
Copy this prompt to try it in V0
Webinar Registration Page
Create a landing page where visitors can register for a Zoom webinar. The API route adds registrants to the Zoom webinar programmatically and sends them a confirmation with the personalized join link.
Design a webinar registration page with the event title, description, date, and a registration form with name and email fields. The 'Register' button should POST to /api/zoom/register and show a confirmation message with the webinar join link returned by the API.
Copy this prompt to try it in V0
Troubleshooting
401 Unauthorized: 'Invalid client_id or client_secret' when requesting access token
Cause: The ZOOM_CLIENT_ID or ZOOM_CLIENT_SECRET environment variable does not match the credentials in your Zoom Marketplace app, or the app is not activated.
Solution: Go to marketplace.zoom.us → Your App → App Credentials. Verify the Client ID and Client Secret match exactly. Ensure the app status is 'Activated'. If the Client Secret was regenerated, update ZOOM_CLIENT_SECRET in Vercel Dashboard and redeploy.
403 Forbidden: 'Insufficient scope' when calling the meetings endpoint
Cause: Your Zoom Server-to-Server OAuth app does not have the required API scopes enabled.
Solution: In Zoom Marketplace, go to your app → Scopes tab. Add meeting:write:admin and meeting:read:admin. Save changes. Note that scope changes on Server-to-Server OAuth apps take effect immediately without requiring a new token.
Meeting is created but shows as 'Instant' type instead of 'Scheduled'
Cause: The start_time is in the wrong format or the meeting type is set to 1 (instant) instead of 2 (scheduled).
Solution: Ensure the start_time in the POST body is in ISO 8601 UTC format: 2026-04-15T14:00:00Z. Confirm the meeting type is 2 in the API route body. Zoom interprets missing or invalid start_time as an instant meeting request.
1body: JSON.stringify({2 type: 2, // 2 = Scheduled meeting3 start_time: new Date(body.startTime).toISOString(),4 timezone: body.timezone,5})Webhook validation fails — Zoom shows 'Unable to connect to endpoint'
Cause: The webhook URL is not publicly accessible (testing locally), the endpoint is returning the wrong response format, or the ZOOM_WEBHOOK_SECRET is incorrect.
Solution: Ensure you are using your Vercel deployment URL (not localhost) as the webhook endpoint. Verify the response includes both plainToken and encryptedToken fields with the correct HMAC-SHA256 computation. Check ZOOM_WEBHOOK_SECRET matches the Secret Token shown in Zoom Marketplace app → Feature → Event Subscriptions.
Best practices
- Use Server-to-Server OAuth for backend integrations — never use personal access tokens or Zoom JWT tokens (deprecated) in new apps
- Cache Zoom access tokens with their expiry time (3600 seconds) to avoid making a token exchange request on every single API call
- Always include a waiting room and passcode on programmatically created meetings to prevent unauthorized access
- Validate webhook signatures using the HMAC-SHA256 challenge to ensure webhook payloads genuinely originate from Zoom
- Store only meeting IDs and join URLs in your database — avoid storing full Zoom API responses which can change structure between API versions
- Test webhook handling with Zoom's built-in webhook test feature in the Marketplace app settings before going live
- Handle the case where a meeting is deleted in Zoom directly (outside your app) by implementing a sync check in your meetings list fetch
Alternatives
Google Meet is better if your users are in the Google Workspace ecosystem and you want tighter Calendar integration, but its API for programmatic meeting creation is more complex.
Microsoft Teams is the better choice when your audience uses Microsoft 365, offering deep integration with Outlook Calendar and OneDrive.
GoToMeeting has a simpler API for basic meeting creation but a smaller developer ecosystem and fewer SDK resources than Zoom.
Webex is preferred in enterprise and government contexts with strict security requirements, offering a robust REST API similar to Zoom's.
Frequently asked questions
Does the Zoom API integration work on a Zoom free plan?
Basic Zoom API access is available on free accounts, but most useful features like scheduled meetings over 40 minutes, API rate limits, and webinar APIs require a Pro plan ($149.90/year) or higher. For production apps, a Pro plan is strongly recommended.
Can I create meetings on behalf of multiple Zoom users in my organization?
Yes. Server-to-Server OAuth with the meeting:write:admin scope lets you create meetings on behalf of any user in your Zoom account. Instead of calling /users/me, call /users/{userId}/meetings where userId is the email or Zoom user ID of the specific host. You must have Zoom admin access on your account.
How do I get the meeting recording after it ends?
Subscribe to the recording.completed webhook event to receive a notification when a recording is available. The webhook payload includes download URLs for the video, audio, and transcript files. You can also poll GET /meetings/{meetingId}/recordings after the meeting ends using the cloud_recording:read:admin scope.
What happens to Zoom meetings created by my app if the Zoom app is deactivated?
Existing meetings continue to function — Zoom meetings exist at the account level, not the API app level. Deactivating the Zoom Marketplace app only prevents your Next.js app from creating new meetings or fetching meeting data via the API. Already-scheduled meetings and their join links remain valid.
Is there a rate limit on the Zoom API?
Yes. Zoom enforces rate limits per account: typically 100 requests per second for most endpoints on Pro plans. The /meetings endpoint has additional limits on meeting creation frequency. If you hit rate limits (HTTP 429), implement exponential backoff in your API route and consider caching GET requests for the meetings list.
Talk to an Expert
Our team has built 600+ apps. Get personalized help with your project.
Book a free consultation