Why This Setup Comes First
Every Zoom to Salesforce integration starts in the same place, and it is not Salesforce. Before your org can list a host's meetings or pull a cloud recording, Zoom needs to know exactly who is calling its APIs and what they are allowed to touch. That knowledge lives in a Server-to-Server OAuth app you build at marketplace.zoom.us, and one wrong click there means your first token request dies with a 400 before Salesforce ever enters the picture.
Clear one thing up first: the Zoom JWT app type is gone, not deprecated. New JWT app creation ended June 1, 2023 and all JWT authorization stopped in September 2023, so any tutorial about an API Key and Secret describes a dead product. Server-to-Server OAuth is the replacement: an account_credentials grant, no browser step, no callback URL, exactly the shape a Salesforce External Credential wants.
This article is the zoomed-in first step of our full technical guide at cloudsheer.com/integrations/zoom/technical-guide. We have shipped this integration across 11 client projects; this is the setup we run every time, down to the click.
What You Need Before You Start
- A Zoom sign-in as the account owner, an admin, or a user whose role has the Server-to-Server OAuth app View/Edit permission (Zoom web portal: Admin > User Management > Roles > Role Settings > Advanced features). Without it, the S2S app type does not appear on the chooser.
- Admin-level Zoom role permissions; :admin scopes only appear in the picker if your role can grant them.
- A Salesforce org where you can reach Setup > Named Credentials and create permission sets. Start in a sandbox.
- A password manager. Zoom keeps its credentials permanently viewable, but Salesforce masks the client secret on save and never shows it again.
- curl or Postman for the token test in Step 5.
- This guide covers org-wide Server-to-Server OAuth; acting as each individual user needs a different Zoom app type plus a Salesforce Auth Provider, covered at the end.
Step 1: Create the Server-to-Server OAuth App
Sign in to https://marketplace.zoom.us with an account that clears the role checklist above.
- Click Developer in the lower-left navigation pane, then on the Created apps page click Develop and select Build an app. Older UI: top-right Develop dropdown, then Build App. Both land on the same chooser.
- Select Server-to-Server OAuth app and click Create. Name it something operational like Salesforce Integration - Prod; the name is internal only, since S2S apps are never published and only work against your own Zoom account.
- If the Server-to-Server OAuth tile is missing or greyed out, stop: your role lacks the permission from the checklist. Fix the role in the Zoom web portal and reload.
Step 2: Copy the Account ID, Client ID, and Client Secret
The new app opens on the App Credentials section.
- Copy the Account ID, Client ID, and Client Secret into your password manager. The Account ID rides on every token request as account_id; the Client ID and Client Secret travel as HTTP Basic auth.
- Unusually, none of these are shown-once: all three stay permanently viewable here, and a regenerated secret leaves the old one working for 30 days in parallel. Treat the secret like a production password anyway: no email, no Slack, no spreadsheet.
- Use the Account ID from this page, not the account number on your Zoom web portal profile. Pasting the profile number is the classic cause of invalid_request on the first token call.
Step 3: Fill In the Information Section
Scroll to Information. This is where stalled setups die, because activation is blocked until it is complete.
- Fill in Short description, Company Name, and Developer Contact Information with a Name and Email address. All are required. Use a team distribution list, not a personal inbox.
- The Feature section is optional: it holds Event Subscriptions for webhooks and a separate Secret Token for webhook signature validation, unrelated to your OAuth client secret.
- Notice what is missing: no redirect URL field exists anywhere in an S2S app. That is by design, and it is why this app type wires into Salesforce without an Auth Provider or callback.
Step 4: Add Granular Scopes
Open the Scopes section, click Add Scopes, search for each scope, tick it, and click Done.
- Apps created since April 2024 offer only granular scopes shaped resource:action:subresource:role; a trailing :admin means account-level. Old tutorials show classic names like meeting:read:admin, which a new app cannot select.
- A solid starter set: meeting:read:meeting:admin, meeting:read:list_meetings:admin, cloud_recording:read:list_user_recordings:admin, cloud_recording:read:list_recording_files:admin, plus user:read:user:admin and user:read:list_users:admin to resolve user IDs.
- If :admin scopes are absent from the picker, your Zoom role is not admin-level: the Step 1 wall, one layer deeper.
- Scope edits never touch tokens already minted. Add a scope, then mint a fresh token, or the API answers 4711 even though the UI shows the scope as granted.
Step 5: Activate the App and Mint a Test Token
Go to Activation and click Activate your app. Zoom refuses to issue tokens for an inactive app, so fix any listed errors, almost always a missing Information field, and retry.
- Mint a token from your terminal: POST https://zoom.us/oauth/token?grant_type=account_credentials&account_id=YOUR_ACCOUNT_ID with header Authorization: Basic base64(client_id:client_secret). No browser, no consent screen, no callback.
- A good response returns access_token, token_type bearer, expires_in 3600, the scope list, and an api_url. There is no refresh token: when the hour is up you mint another, so cache each token for about 55 minutes.
- Prove it works: GET https://api.zoom.us/v2/users?page_size=1 with Authorization: Bearer YOUR_TOKEN should return a 200 and one user.
- Old forum advice that a new token revokes the previous one is obsolete; each S2S token now lives out its own hour independently.
Step 6: Create the External Credential in Salesforce
Now Salesforce. In Setup, type Named Credentials into Quick Find, open it, switch to the External Credentials tab, and click New.
- Label and Name: Zoom_S2S. Authentication Protocol: OAuth 2.0. Authentication Flow Type: Client Credentials with Client Secret.
- Identity Provider URL: https://zoom.us/oauth/token?grant_type=account_credentials&account_id=YOUR_ACCOUNT_ID. The grant type and account ID ride in the query string because Salesforce hardcodes grant_type=client_credentials in the request body.
- Leave Scope blank, since S2S scopes come from the Zoom app, not the request. Leave Pass client credentials in request body unchecked so Salesforce sends HTTP Basic auth, which Zoom expects.
- Neither vendor officially documents this query-string pattern. It has held up across our deployments, but validate in a sandbox; if the token call 400s with unsupported grant type, fall back to an Apex-managed token from protected custom metadata, cached in Platform Cache.
- Under Principals, click New: Parameter Name ZoomS2S, Sequence Number 1, then the Client ID and Client Secret from Step 2, and Save. Salesforce masks the secret on save and never redisplays it, so confirm it is in your vault first.
Step 7: Grant Access, Create the Named Credential, and Smoke Test
Two more Salesforce objects and you are done. The first is the step everyone forgets.
- Setup > Permission Sets > your integration permission set > External Credential Principal Access > Edit > add the ZoomS2S principal > Save, then assign the set to the running user. Skip this and every callout fails as a bare 401.
- On the Named Credentials tab, click New. Label and Name: Zoom_S2S. URL: https://api.zoom.us. External Credential: Zoom_S2S. Keep Generate Authorization Header checked.
- Smoke test from Developer Console > Debug > Open Execute Anonymous Window: HttpRequest req = new HttpRequest(); req.setEndpoint('callout:Zoom_S2S/v2/users?page_size=1'); req.setMethod('GET'); System.debug(new Http().send(req).getBody());
A 200 with a JSON users list means Salesforce minted the Zoom token, attached the Bearer header, and will keep refreshing it automatically. Every future endpoint looks like callout:Zoom_S2S/v2/users/me/meetings, with no token code in your org.
Common Errors and How to Fix Them
- unsupported grant type, a 400 from zoom.us/oauth/token: the grant is bound to the app type, so you used a user-authorized app's credentials, or Salesforce's body grant_type overrode your query string. Fix: confirm you hold S2S credentials; if the External Credential keeps failing, switch to the Apex-managed fallback.
- invalid_request or bad request on the token call: account_id is missing or wrong, usually the profile-page account number. Fix: use the Account ID string from App Credentials.
- invalid_client, invalid client id or client secret: doubled-up credentials, classically Postman sending its Authorization tab plus a manual Basic header. Fix: send exactly one Basic header of base64(client_id:client_secret).
- The application is disabled: the S2S app was deactivated, which also killed every outstanding token instantly (API calls start failing with code 124, Invalid access token). Fix: reactivate under Activation and mint new tokens.
- Code 4711, Invalid access token, does not contain scopes: the token predates the scope, because S2S tokens snapshot scopes at issuance. Fix: add the exact granular scope named in the error, then mint a new token.
- Account does not enabled REST API, Zoom's wording, not ours: the token was minted without account_id, often by a generic OAuth2 client-credentials helper. Fix: add grant_type=account_credentials and account_id back to the token POST.
- Salesforce callout returns 401 with an OAuth token error: the token request went out as pure client_credentials, the principal's secret was re-saved wrong, or the user lacks External Credential Principal Access. Fix: re-check the Identity Provider URL, re-enter the full secret, and verify the permission set, the usual culprit.
Security Checklist Before You Ship
- Scope to the minimum: only the granular scopes your callouts use, and no write scopes on a read-only integration.
- Grant External Credential Principal Access through a dedicated integration permission set on a dedicated integration user, never a broad profile.
- Rotate the client secret on a schedule; the 30-day dual-validity window makes rotation zero-downtime: regenerate, update the principal, smoke test.
- Document the kill switch: deactivating the Zoom app invalidates every outstanding token instantly, mid-hour; it is your fastest incident response and your fastest self-inflicted outage.
- Park app ownership on an admin-level service account. If the owner's Zoom role is downgraded, Zoom silently strips :admin scopes from the app with no warning on the Salesforce side.
Where This Fits in the Full Zoom Build
Once the Named Credential answers 200, the real work starts: syncing meetings onto Salesforce Events, pulling recording links onto Activities, subscribing to webhooks via the Feature section. The full architecture and Apex patterns live in our technical guide at cloudsheer.com/integrations/zoom/technical-guide.
Two boundaries worth knowing. An S2S app acts as your account, not a person: creating meetings as each rep needs Zoom's user-authorized General app type plus a Salesforce Auth Provider (Open ID Connect) with a Browser Flow External Credential, and the authcallback URL registered in both Redirect URL for OAuth and the OAuth Allow List. If you only need the standard experience, Zoom ships a managed Zoom for Salesforce package on the AppExchange.
If you build with Claude Code, we packaged this setup as a free skill: npx skills add shivamgoel-cloudsheer/Claude-skills --skill zoom-salesforce. It encodes the click path, the granular scope names, and the Apex fallback so your agent does not reproduce a JWT tutorial from 2022.
How Cloudsheer Can Help
Cloudsheer has shipped Zoom to Salesforce integrations across 11 client projects, from meeting sync to webhook-driven recording pipelines. Every trap in this article, the grant-type smuggling, the 4711 scope snapshots, the forgotten principal access, is one we hit in a real org and now design around by default.
If you want this integration built, audited, or rescued, we can scope it fast: the credential setup is a day of work, and a full meetings-and-recordings sync is typically a two to three week engagement. Book a free discovery call at cal.com/cloudsheer-consulting/30min.
