Guidance on Handling Zoom Meeting Rate Limits for High-Volume User Registrations

Before Creating a New Topic:

If you’re experiencing unexpected API/Zoom API Events (webhooks) behavior please search the forum with relevant keywords (e.x. error message) and follow the guidance outlined in those posts. Please also leverage the following support links:


API Endpoint(s) and/or Zoom API Event(s)

We are working with the following Zoom API endpoints:

Description

We have developed a registration form in Laravel with the following fields:

  • First Name
  • Last Name
  • Email Address

Workflow:

  1. User submits the form → We validate the input using Google reCAPTCHA.
  2. Check if the user exists on Zoom
  • Calls GET https://api.zoom.us/v2/users/{email} to check if the user exists.
  • If the user exists, retrieves their Zoom user ID.
  • If the user is not found (404 error), proceeds to create a new user.
  1. Create Zoom User (if needed)
  • Calls POST https://api.zoom.us/v2/users with action: custCreate.
  • Provides user details (email, first_name, last_name, password (randomly generated)).
  1. Create a Zoom meeting for the user
  • Calls POST https://api.zoom.us/v2/users/me/meetings to schedule a meeting.
  • Sets topic, type, start_time, duration, timezone, and other settings.
  1. Handling Repeat Registrations
  • If the same user submits the form again (email already exists), we create a new meeting and send the meeting details to the user via email.
  • Afterward, the user is redirected to a Thank You page.

Error?

When creating multiple meetings, we are encountering a rate limit issue:
Failed to create meeting: Client error: POST https://api.zoom.us/v2/users/me/meetings resulted in a 429 Too Many Requests response: {“code”:429,“message”:"You have exceeded the daily rate limit (100) of Meeting Create/Update API requests permitted for (truncated…)}

How To Reproduce

Request URL & Headers (without sensitive data):
Check if user exists:

GET https://api.zoom.us/v2/users/{email}
Headers:  
- Authorization: Bearer {access_token}  
- Content-Type: application/json

Create new user (if not found):

POST https://api.zoom.us/v2/users
Headers:  
- Authorization: Bearer {access_token}  
- Content-Type: application/json  

Body:
{
    "action": "custCreate",
    "user_info": {
        "email": "user@example.com",
        "first_name": "John",
        "last_name": "Doe",
        "password": "random@123"
    }
}

Authentication method or app type:

  • We are using OAuth authentication with account-level credentials.
  • The access token is generated via:
POST https://zoom.us/oauth/token?grant_type=account_credentials&account_id={ZOOM_ACCOUNT_ID}

Error:
HTTP 429 Too Many Requests
{
“code”: 429,
“message”: “You have exceeded the daily rate limit (100) of Meeting Create/Update API requests permitted.”
}

Additional Question

We are running multiple campaigns where we expect a high volume of user registrations, ranging from 100 to 1,000+ users per day. Each user who registers needs to have a Zoom meeting created for them. Given Zoom’s current daily rate limit of 100 meeting creation/update requests, this restriction poses a significant challenge for our workflow.

To ensure seamless user onboarding without interruptions, we need guidance on the best approach to handle this scenario. Specifically, we would like to understand:

  1. Rate Limit Increase – Is there an option to request a higher rate limit for meeting creation to accommodate our scale? If so, what is the process for requesting an increase?
  2. Alternative Solutions – Are there alternative ways to schedule meetings at scale, such as:
  • Using Zoom Webinars instead of individual meetings?
  • Creating recurring meetings with unique join links for users instead of separate meetings?
  • Assigning users to pre-existing meetings instead of creating a new one each time?
  1. Best Practices for High-Volume Meeting Scheduling – What is the recommended approach to avoid hitting the rate limit while still ensuring that each user gets a valid meeting link?

We would appreciate any recommendations or solutions to ensure that both user creation and meeting scheduling remain uninterrupted, even with large-scale registrations. Thank you!

1 Like

Hi there,

I think one of two things are happening:

  1. You’re hitting the limit on meeting creation due to using POST to https://api.zoom.us/v2/users/me/meetings when https://api.zoom.us/v2/users/<user email address>/meetings might be more appropriate (rather than creating the meeting as your API user on behalf of the user, instead create it directly as the user). I recently wrote a response to another user with a similar issue here.

I don’t disagree that it’d be nice to have an option to pay a little more for tiers of increased Meetings API daily limits.

  1. You’re hitting the limit on the user creation endpoint using the create action:
  • 50 requests per day for Free accounts.
  • 1,500 requests per day for Pro accounts.
  • 10,000 requests per day for Business+ accounts.

But if you’re actually using the action custCreate you must be working with Zoom ISV which surely has higher limitations?

Hope this helps!

Hi Alex

Thank you for your suggestion! We will apply the recommended API endpoint changes and test them accordingly.

We are using the custCreate method because we do not want to create full Zoom accounts for users. Instead, their details are stored within our system as registered users, allowing us to track registrations via the API as “web-tagged” users. However, they can still join meetings using the start_url and join_url without requiring a Zoom account.

We chose not to use the create method because it requires users to receive a confirmation email, activate their Zoom account, and set a password before they can join meetings—adding unnecessary steps to our workflow.
Similarly, we did not use autoCreate because it is intended for Enterprise customers and also requires account activation before meeting links can be generated.

We currently have the Zoom Workplace Business Plus Plan on our account, so I believe there is no need to upgrade for increased Meetings API daily limits, as you mentioned that Business+ accounts already have a limit of 10,000 requests per day.

However, we have not yet applied for the Zoom ISV Partner Program, so we will proceed with our current approach for now.

One clarification: If we enroll in the Zoom ISV Partner Program, will our Meetings API daily limits be increased automatically?

Also, I am attaching the current code we have at this time. Please let me know if anything needs to be corrected, except for the API endpoint.





    /**
     * Create a Zoom Meeting
     */
    public function createMeeting(Request $request): array
    {
        // Validate input
        $validated = $this->validate($request, [
            'title' => 'required',
            'start_date_time' => 'required|date',
            'duration_in_minute' => 'required|numeric'
        ]);

        try {
            $response = $this->client->post('https://api.zoom.us/v2/users/me/meetings', [
                'headers' => [
                    'Authorization' => 'Bearer ' . $this->generateToken(),
                    'Content-Type' => 'application/json',
                ],
                'json' => [
                    'topic' => $validated['title'],
                    'type' => 2, // 2 for scheduled meeting
                    'start_time' => Carbon::parse($validated['start_date_time'])->toIso8601String(),
                    'duration' => $validated['duration_in_minute'],
                ],
            ]);

            return json_decode($response->getBody()->getContents(), true);

        } catch (RequestException $e) {
            throw new \Exception($e->getResponse()->getBody()->getContents());
        }
    }

    public function createUser(Request $request)
    {

        // Validate input with custom error messages
        $validated = $request->validate([
            'first_name' => 'required|string',
            'last_name' => 'required|string',
            'email' => 'regex:/^([a-z0-9\+_\-]+)(\.[a-z0-9\+_\-]+)*@([a-z0-9\-]+\.)+[a-z]{2,6}$/ix',
            'page_url' => 'nullable|string',
            'g-recaptcha-response' => 'required', // Ensure captcha is submitted
        ], [
            'g-recaptcha-response.required' => 'Captcha verification failed: Please complete the captcha.', // Custom error message
        ]);
        
        $email = $validated['email'];
        $pageUrl = $validated['page_url'] ?? ''; // Ensure pageUrl is always set
        $captcha = $validated['g-recaptcha-response']; // Use validated data
        
        $secretKey = "*************************"; // Your Secret Key
        $response = file_get_contents("https://www.google.com/recaptcha/api/siteverify?secret=$secretKey&response=$captcha");
        $responseKeys = json_decode($response, true) ?? [];
    
        // Verify reCAPTCHA success
        if (!isset($responseKeys["success"]) || !$responseKeys["success"]) {
            $redirectUrl = $pageUrl ?: 'https://xxx.com/';
            return redirect($redirectUrl)->with('error', 'Captcha verification failed: Please complete the captcha.');
        }
        
        try {
            $response = $this->client->get("https://api.zoom.us/v2/users/$email", [
                'headers' => [
                    'Authorization' => 'Bearer ' . $this->generateToken(),
                    'Content-Type' => 'application/json',
                ],
            ]);
            $user = json_decode($response->getBody()->getContents(), true);
            $zoomUserId = $user['id'];
    
        } catch (RequestException $e) {
            // If user does not exist, Zoom returns 404
            if ($e->getResponse()->getStatusCode() == 404) {
    
                $password = Str::random(10) . '@1A';
                try {
                    $response = $this->client->post('https://api.zoom.us/v2/users', [
                        'headers' => [
                            'Authorization' => 'Bearer ' . $this->generateToken(),
                            'Content-Type' => 'application/json',
                        ],
                        'json' => [
                            'action' => 'custCreate',
                            'user_info' => [
                                'email' => $email,
                                'type' => 1,
                                'first_name' => $validated['first_name'],
                                'last_name' => $validated['last_name'],
                                'password' => $password,
                            ],
                        ],
                    ]);
    
                    $data = json_decode($response->getBody()->getContents(), true);
                    $zoomUserId = $data['id'];
    
                } catch (RequestException $e) {
                    return redirect()->back()->with('error', "Failed to create Zoom user: " . $e->getMessage());
                }
            } else {
                return redirect()->back()->with('error', "Failed to check user: " . $e->getMessage());
            }
        }
    
        // Register the user for a meeting
        try {
            $meetingResponse = $this->client->post("https://api.zoom.us/v2/users/me/meetings", [
                'headers' => [
                    'Authorization' => 'Bearer ' . $this->generateToken(),
                    'Content-Type' => 'application/json',
                ],
                'json' => [
                    'topic' => "Training Session",
                    'type' => 2,
                    'start_time' => Carbon::now()->addDays(1)->toIso8601String(), // Example: meeting starts in 1 day
                    'duration' => 60, // 60 minutes
                    'timezone' => 'UTC',
                    'agenda' => 'Training session for new users',
                    'settings' => [
                        'host_video' => true,
                        'participant_video' => true,
                        'mute_upon_entry' => true,
                    ],
                ],
            ]);
    
            $meetingData = json_decode($meetingResponse->getBody()->getContents(), true);
            $joinUrl = $meetingData['join_url'];
    
        } catch (RequestException $e) {
            return redirect()->back()->with('error', "Failed to create meeting: " . $e->getMessage());
        }
    
        Mail::to($validated['email'])->send(new ZoomMeetingMail($validated['first_name'], $meetingData['join_url'], $pageUrl));
        return redirect()->route('thank.you.zoom')->with('success', "You’re Registered! Check your inbox for details.");

    }
1 Like

That’s a good question! I would hope so, unfortunately I don’t have any experience with the ISV product so I would wait to see if someone from Zoom has an answer. I also didn’t realize that custCreate could be accessed without ISV (I just assumed that it’d return an error 403 or something without it).

Your code looks great aside from the hardcoded secret key (consider .ENV files or an online solution like AWS Systems Manager Parameter Store to avoid your API keys and secrets from being committed to your code repo and potentially leaked); Otherwise I thought it was pretty clever to work in your recaptcha requirement into your validation logic.

Hi Alex

Could you ask or tag someone who can confirm the limit part with ISV? Also, the custCreate method was working the last time I checked. The users were successfully created via the API call and were found in the list of Zoom users, tagged with “web.”

Sure!

@elisa.zoom do you or one of your associates know the answer to Suzanne’s question re: ISV and daily meeting limitations?:

I’d be curious to know as well. TIA

Hey all!
Sorry missed this thread!
I will ask internally and will get back to you!

Hey @Suzanne
Just to update you here, joining the Zoom ISV Partner program won’t increase your daily rate limits

This topic was automatically closed 24 hours after the last reply. New replies are no longer allowed.