Zoom Marketplace OAuth setup
CoreVideo uses Zoom Public Client OAuth + PKCE for user sign-in and a server-side broker to mint short-lived Meeting SDK JWTs for the helper process. This keeps OAuth and Meeting SDK secrets out of the OBS plugin while still supporting attributed joins, external-account meetings, and Marketplace review.
Published builds use an HTTPS OAuth broker at corevideo.iamfatness.us. The OBS plugin only knows the broker start URL. End users never enter app credentials or client secrets.
Zoom Marketplace app (publisher, one-time)
- Create a General app in the Zoom App Marketplace.
- Enable User-managed OAuth.
- Add this Redirect URL:
https://corevideo.iamfatness.us/oauth/callback - Add the same value to the OAuth allow list:
https://corevideo.iamfatness.us/oauth/callback - Add the minimum scopes used by the build:
user:read:tokenuser:read:user - Enable Meeting SDK / Embed for the same app/environment.
- Confirm beta or production access is approved for the accounts that will test or use the app.
Broker configuration
The Cloudflare Worker serving corevideo.iamfatness.us must have these secrets:
ZOOM_OAUTH_PUBLIC_CLIENT_ID=<Marketplace Public Client ID>
ZOOM_OAUTH_AUTHORIZE_URL=https://marketplace.zoom.us/v2/authorize
ZOOM_OAUTH_REDIRECT_URI=https://corevideo.iamfatness.us/oauth/callback
ZOOM_OAUTH_SCOPES=user:read:token user:read:user
COREVIDEO_OAUTH_BROKER_SECRET=<random 32+ byte secret>
ZOOM_MEETING_SDK_CLIENT_ID=<Meeting SDK client/app key for this environment>
ZOOM_MEETING_SDK_CLIENT_SECRET=<Meeting SDK client/app secret for this environment>
The Meeting SDK secret is stored only as a Cloudflare Worker secret. It must not be committed, baked into the OBS plugin, or entered by end users.
Embedding the app identity into the build (publisher)
The OAuth broker URL is part of the published app's identity, not a per-user setting. CoreVideo bakes it in at compile time:
cmake -B build \
-DZOOM_EMBED_OAUTH_AUTHORIZATION_URL=https://corevideo.iamfatness.us/oauth/start ...
In CI, pass the values as GitHub Actions secrets so they never land in the source tree. They are written into src/zoom-credentials.h from src/zoom-credentials.h.in and read by ZoomPluginSettings::load().
When embedded values are present, they win over OBS global.ini so a stale local config cannot change the published app identity. Developers can still use global.ini overrides only in local builds where the embedded values are blank.
End-user sign-in
- Install a CoreVideo build that has the app identity embedded.
- Open OBS, then open Tools > Zoom Plugin Settings.
- In the Zoom Account section click Sign in with Zoom and approve the app in the browser. There are no Client ID, Client Secret, or Authorization URL fields to configure; the build already knows the broker URL.
- The callback helper (
CoreVideoOAuthCallback.exeon Windows,CoreVideoOAuthCallback.appon macOS) is registered for thecorevideo://URL scheme the first time you click Sign in and forwards the redirect to the running plugin on127.0.0.1:<ControlServerPort>(default19870).
Runtime flow
- CoreVideo opens the system browser at the embedded broker start URL with a local
stateandreturn_uri=corevideo://oauth/callback. - The broker generates a PKCE verifier/challenge and redirects to
https://marketplace.zoom.us/v2/authorizewithredirect_uri=https://corevideo.iamfatness.us/oauth/callback. - Zoom redirects back to the broker.
- The broker returns an encrypted, short-lived broker token containing the authorization code to
corevideo://oauth/callback. - The callback helper forwards that URL to the running plugin. The plugin verifies
state, redeems the broker token over HTTPS, and the broker exchanges the code for access/refresh tokens using Public Client OAuth:client_idandcode_verifierin the form body, with no client secret and no Authorization header. - Before joining a meeting, the plugin refreshes the access token through the broker if needed and calls
/oauth/sdk-jwt. - The broker validates the OAuth access token against
GET https://api.zoom.us/v2/users/me, then signs a short-lived Meeting SDK JWT with the server-side Meeting SDK client ID/secret. - The plugin starts
ZoomObsEnginewith that SDK JWT and joins the meeting. The helper uses Zoom's default Meeting SDK window, so the operator can admit waiting-room participants, manage self video/audio, and use normal meeting controls.
Security notes
- No OAuth or Meeting SDK client secret is shipped in the binary. The settings dialog does not expose Client ID, Client Secret, or Authorization URL fields for published builds, so users cannot misconfigure the integration.
- Broker state is HMAC-signed and expires after 10 minutes. Broker result tokens are AES-GCM encrypted, contain only the authorization code, and expire after 5 minutes.
- Windows token storage uses DPAPI before writing tokens into OBS global config.
- Refresh tokens are rotated; always persist the latest refresh token Zoom returns.
- Windows builds must ship Qt's TLS backend plugins, especially the Schannel backend under
obs-plugins/64bit/plugins/tls, or OAuth HTTPS requests will fail before broker tokens or SDK JWTs can be fetched. - The URL callback command bypasses the local control-server token, but the OAuth
stateis still required before any broker token can be redeemed. - Do not log access tokens, refresh tokens, SDK JWTs, authorization codes, broker tokens, OAuth state values, or Meeting SDK secrets.
Marketplace review checklist
- Explain that CoreVideo joins meetings as an OBS capture/ISO recording tool.
- Request only the scopes used by the build.
- Provide test credentials and a test meeting hosted outside the app account.
- Document the visible in-product OAuth sign-in and uninstall/disconnect path.
- Make sure the Marketplace listing explains when meeting audio/video is captured, where it is processed, and that raw media stays local unless OBS outputs it.