Documentation
Zoom integration — install, use, and remove
Source: docs/ZOOM.md
#Zoom integration — install, use, and remove
Kindryn's Zoom integration lets community admins host events and coaching sessions on their existing Zoom subscriptions. Once connected, Kindryn schedules the meeting on Zoom, sends real iCal invites to RSVPed members, and serves a per-click join URL so members never share the raw Zoom link publicly.
This guide covers the full Zoom App Marketplace lifecycle:
- Prerequisites
- Installing — connect a Zoom account
- Using — hosting events and coaching sessions on Zoom
- Multi-account routing
- Webhook events Kindryn subscribes to
- Removing — disconnect a Zoom account
- Reviewer test plan (Zoom App Marketplace)
- Troubleshooting
#1. Prerequisites
- A Kindryn community where you have OWNER or ADMIN role.
- A Zoom account in good standing on a paid or free plan. Cloud recording is required if you want Kindryn to archive recordings on your behalf — free plans cannot record to the cloud, so the cloud-recording warning on the integrations page is informational.
- Your Kindryn instance operator must have set
ZOOM_CLIENT_ID,ZOOM_CLIENT_SECRET, andZOOM_REDIRECT_URIin environment configuration. If you self-host Kindryn, follow your instance's deployment docs. If you're on the hostedkindryn.appservice this is already configured.
#2. Installing — connect a Zoom account
- Sign in to your Kindryn community and open Admin → Integrations. The shortcut URL is
/<your-community-slug>/admin/integrations. - Scroll to the Video & meetings section. Click Connect Zoom.
- You will be redirected to Zoom's OAuth consent screen. Sign in with the Zoom account that will host meetings, and approve the requested scopes (least-privilege set per Zoom Marketplace review):
meeting:write:meeting— create meetings on behalf of the connected host.meeting:write:registrant— add Kindryn members as registrants on RSVP so each member gets a personalized one-click join URL.meeting:update:meeting— patch the scheduled meeting (topic, start time, alternative hosts).meeting:update:livestream— configure the meeting's custom livestream destination when an event is bridged to the Cloudflare-Stream replay surface.meeting:update:livestream_status— start / stop the configured livestream relay onmeeting.started.meeting:update:registrant_status— cancel a registrant when a member changes their RSVP to "Not going".meeting:delete:meeting— cancel the meeting on Zoom when the Kindryn event is deleted or rescheduled.meeting:read:meeting— required to subscribe to themeeting.started/meeting.endedwebhooks Kindryn uses to mark the event live and trigger post-event hooks.cloud_recording:read:recording— required to subscribe to therecording.completed/recording.transcript_completedwebhooks (no-op if cloud recording is disabled).cloud_recording:delete:meeting_recording— trash the cloud recording on Zoom after Kindryn pulls the artifact to its own storage, so your Zoom cloud quota isn't burned.user:read:user— read the installer's basic profile once during OAuth callback to bind the grant to a Zoom account.user:read:settings— read recording settings once to surface the "cloud recording enabled" warning in the integrations card.user:read:list_users:admin— enumerate the licensed users on the connected account so the event-create flow can offer every available host (not just the installer) in the host picker.
- Zoom redirects you back to Kindryn. The newly connected account appears in the Video & meetings card with the email of the installing Zoom user and the list of hosts available on that Zoom account.
That's it — the account is now ready to host Kindryn-scheduled meetings.
#3. Using — hosting events and coaching sessions on Zoom
#Events
- In the admin shell, open a space and create or edit an event.
- Set Delivery mode to Zoom.
- Pick the Zoom host from the dropdown (this is one of the licensed hosts on your connected Zoom account).
- Save the event. Kindryn calls Zoom's
POST /users/{host}/meetingsendpoint and stores the returnedmeetingIdandjoinUrlon the event row. - When members RSVP, Kindryn sends them an iCal invite via email. The calendar event contains a per-member join URL routed through Kindryn — this prevents the raw Zoom link from being shared publicly.
#Coaching sessions
The same flow applies to 1:1 coaching bookings and recurring group coaching series. Selecting Zoom as the delivery mode triggers the same meeting creation flow. Each coaching booking gets its own meeting on the coach's Zoom calendar.
#Join routing — anti-share
When a member clicks the "Join Zoom" button on an event or coaching page, Kindryn POSTs to an internal zoom-join endpoint that:
- Verifies the caller is the RSVPed/booked member.
- Verifies the meeting state (cancelled / no-show / etc.).
- Mints a tracked join URL and redirects.
The raw zoomJoinUrl is never sent down to the client — only the indirection URL is. This stops members from forwarding their join link to non-members.
#4. Multi-account routing
Kindryn supports connecting multiple Zoom accounts to a single community. Each event picks a host from any connected account. This is useful when:
- Several coaches each have their own Zoom subscription.
- You have a production Zoom account for paid events and a free account for internal team meetings.
- You're moving from one Zoom account to another and want both live during the transition.
To add another account, repeat step 2 with a different Zoom login. Both accounts will list in the Video & meetings card.
#5. Webhook events Kindryn subscribes to
Kindryn registers a single webhook endpoint per Zoom account:
POST https://<your-kindryn-host>/api/integrations/zoom/webhooks
Subscribed event types:
| Zoom event | What Kindryn does |
|---|---|
meeting.started | Marks the meeting as live in Kindryn; opens the per-event chat / Q&A. |
meeting.ended | Marks the meeting as ended; closes the live chat; triggers post-event hooks. |
meeting.participant_joined | Records attendance for the joining member. |
meeting.participant_left | Computes duration; closes the attendance row. |
recording.completed | Stores the cloud recording URL on the event for later playback (if enabled). |
endpoint.url_validation | Standard Zoom CSP challenge — Kindryn echoes the encrypted token back. |
All webhook deliveries are HMAC-signed using your account's Zoom Webhook Secret. Kindryn verifies every request before processing.
#6. Removing — disconnect a Zoom account
- Open Admin → Integrations at
/<your-community-slug>/admin/integrations. - Find the Zoom account row in the Video & meetings card.
- Click Disconnect.
Kindryn will:
- Call Zoom's
oauth/revokeendpoint to invalidate the access and refresh tokens on Zoom's side. - Null out the stored tokens, host list, and webhook secret on the Kindryn database row.
- Leave any historical events and meetings intact — they will simply no longer link to a live Zoom meeting. Members who try to join an already-scheduled meeting on a disconnected account will see a "Meeting unavailable" message.
You can also uninstall the Kindryn app directly from your Zoom account at Zoom Marketplace → Manage → Installed Apps → Kindryn → Uninstall. Doing so revokes the tokens on Zoom's side; Kindryn will auto-detect the revocation on the next API call and mark the integration as disconnected.
#7. Reviewer test plan (Zoom App Marketplace)
This section is for the Zoom Marketplace review team. Run it top-to-bottom — every requested scope is exercised by exactly one step.
#Reviewer credentials
Two ways in:
- Password credential — provided in the Marketplace submission form. Sign in at
https://kindryn.app/auth/login. - Google SSO with
[email protected]— the account you mentioned in correspondence has been pre-seeded as ADMIN on the same workspace. Click "Continue with Google" on the login page.
Either path lands you in the Zoom Reviewer Workspace community at https://kindryn.app/zoom-reviewer. The workspace is pre-seeded with one space (Reviewer events) and one virtual event titled "Demo event — switch delivery to Zoom" scheduled for the next Wednesday at 11:00.
#Step 1 — Connect your Zoom account (scopes: user:read:user, user:read:settings, user:read:list_users:admin)
- Open
https://kindryn.app/zoom-reviewer/admin/integrations. - Scroll to the Video & meetings section, click Connect Zoom.
- Approve the OAuth consent. Zoom redirects back to Kindryn.
- The card now shows your Zoom email + an enumerated list of host seats on your Zoom account. (Exercises
user:read:userfor the installer profile,user:read:settingsfor the cloud-recording capability badge,user:read:list_users:adminfor the host seat enumeration.)
#Step 2 — Provision a Zoom meeting on the seeded event (scopes: meeting:write:meeting, meeting:update:meeting)
- From the workspace home, open the Reviewer events space.
- Click the Demo event — switch delivery to Zoom card.
- Click Edit in the event header.
- The "Virtual event" toggle is already ON in the seed — the Where does this event run? section is visible. (Note: in newly-created events the toggle starts OFF, and the Delivery section is hidden until it's toggled. The seed event removes that hop for you.)
- In Where does this event run?, switch from Native (LiveKit) to Zoom.
- Pick a Zoom host from the dropdown.
- Click Save. Kindryn calls
POST /users/{id}/meetingsagainst your Zoom account (meeting:write:meeting). The event row now storeszoomMeetingId+zoomJoinUrl. - Re-open the event for edit, change the title, save again. Kindryn calls
PATCH /meetings/{id}(meeting:update:meeting).
#Step 3 — RSVP a member to register them on Zoom (scopes: meeting:write:registrant, meeting:update:registrant_status)
- From the event page, click RSVP to register yourself. Kindryn calls
POST /meetings/{id}/registrantsso Zoom mints a personalized join URL bound to your email (meeting:write:registrant). - Change your RSVP to Not going. Kindryn calls
PUT /meetings/{id}/registrants/statuswithaction=cancel(meeting:update:registrant_status).
#Step 4 — Cloudflare Stream simulcast (scopes: meeting:update:livestream, meeting:update:livestream_status)
- Re-open the event Edit page.
- In the Zoom delivery block, toggle Simulcast to Kindryn page ON. Save. Kindryn provisions a Cloudflare Stream live input + persists the RTMPS URL on the event.
- Start the meeting in your Zoom client. Zoom posts
meeting.startedto Kindryn's webhook, which callsPATCH /meetings/{id}/livestreamto set the RTMP destination (meeting:update:livestream), thenPATCH /meetings/{id}/livestream/statuswithaction=start(meeting:update:livestream_status). The event page now shows the live HLS player.
#Step 5 — Webhooks land on Kindryn (scopes: meeting:read:meeting, cloud_recording:read:recording)
The integration auto-subscribes to meeting.started, meeting.ended, meeting.participant_joined, meeting.participant_left, recording.completed, and recording.transcript_completed. These subscriptions require meeting:read:meeting (for the meeting lifecycle events) and cloud_recording:read:recording (for the recording events). End the meeting in Zoom; Kindryn flips the event status and closes the live chat in response to meeting.ended.
#Step 6 — Trash the cloud recording after pull (scope: cloud_recording:delete:meeting_recording)
When a recording finishes processing on Zoom and Kindryn finishes pulling it down to MinIO, Kindryn calls DELETE /meetings/{uuid}/recordings?action=trash so the Zoom account's cloud-storage quota isn't consumed by content already mirrored in Kindryn (cloud_recording:delete:meeting_recording). This step requires cloud recording to be enabled on your Zoom account; if it isn't, the warning banner on the integrations card is informational and steps 5–6 around recording are no-ops.
#Step 7 — Cancel the meeting on Zoom from Kindryn (scope: meeting:delete:meeting)
- From the event page header, click Cancel event.
- Confirm. Kindryn calls
DELETE /meetings/{id}on Zoom (meeting:delete:meeting).
#Step 8 — Disconnect (clean teardown)
- Open
/zoom-reviewer/admin/integrations. - Find the Zoom card. Click Disconnect.
- Kindryn calls Zoom's
oauth/revokeendpoint, nulls out the stored tokens, and removes the host list.
#Need a paid Zoom tier?
Steps 1–3 + step 7 work on a free Zoom plan. Steps 4 (livestream) + 6 (cloud recording cleanup) need a Zoom plan that supports those features — they're no-ops on free accounts.
If you'd prefer Kindryn cover the Zoom subscription for review, reply on the submission thread and we'll provision a Zoom account on the required tier and share its credentials separately. We will not upgrade [email protected] since that's your account, not ours.
#8. Troubleshooting
#"Zoom integration is not configured on this Kindryn instance"
The instance operator has not set the required Zoom OAuth environment variables. Contact your Kindryn admin / operator and ask them to configure ZOOM_CLIENT_ID, ZOOM_CLIENT_SECRET, and ZOOM_REDIRECT_URI.
#"Cloud recording not enabled on this Zoom account"
This warning shows when the connected Zoom account doesn't have cloud recording in its plan. Meetings hosted via Kindryn will not be auto-recorded. Either upgrade your Zoom plan or ignore the warning if you don't need recording.
#Members report a broken join link
- Confirm the meeting hasn't been cancelled in Zoom directly (cancelling from Zoom doesn't propagate back to Kindryn — always cancel from the Kindryn event page).
- Check the event row in
/adminto confirmzoomMeetingIdis set. - If the row exists but joins still fail, the OAuth tokens may have expired. Reconnect the Zoom account via the integrations page.
#Recording never appears on the event
Cloud recording must be enabled on the Zoom account. Also, recordings take a few minutes to process after the meeting ends — Kindryn writes the playback URL when the recording.completed webhook fires, which can be 5–15 minutes after the meeting ends depending on duration.
#Support
For Kindryn-side issues with the Zoom integration, see kindryn.app/support. For Zoom-side issues (account limits, recording policies, webhook delivery from Zoom's end), contact Zoom support directly.
