API Endpoint(s) and/or Zoom API Event(s)
meeting.participant_joined webhook event — Meetings Webhooks - API - Zoom Developer Docs
Description
We’re building a server-side service that uses the meeting.participant_joined webhook to detect and remove unwanted notetaker bots (Otter, Fireflies, Fathom, etc.) from meetings, while admitting legitimate human participants. The webhook is our only data source for the join-time decision.
The challenge is that for a meaningful share of joins, the payload comes through with email empty and id empty. From our observations this happens for at least three distinct participant types:
- Browser/web-client joiners who aren’t signed in
- Notetaker bots that use the Meeting SDK without registering a Marketplace app
- Anonymous guests joining via meeting link
Two specific questions:
- Is there any field in the
participant_joinedpayload (or a related event/endpoint) that reliably distinguishes a Meeting SDK joiner from a web-client joiner? For example, a client-type indicator, an SDK app identifier, or something equivalent that doesn’t depend on the participant having an email. participant_uuidappears to be session-scoped — it changes when the same participant leaves and rejoins the same meeting. Is there a documented identifier that persists across re-entry within a single meeting instance for the same underlying participant, regardless of whether they’re signed in?
Error?
No error, just a question.
How To Reproduce
Steps to reproduce the behavior:
- Subscribe a Webhook-only app to the
meeting.participant_joinedevent. - Authentication: Server-to-Server OAuth app with the
meeting:read:adminscope (also reproduces with a Webhook-only app using the event subscription alone). - Start a meeting on a paid account and have three participants join:
- Participant A: a Meeting SDK bot joining without signing in (no Marketplace app registration)
- Participant B: a browser/web-client user joining via meeting link without signing in
- Participant C: a signed-in user from the Zoom desktop client
- Inspect the three
participant_joinedpayloads. In our testing, A and B both arrive withemailempty,idempty,participant_uuidsession-scoped, with no field we’ve found that distinguishes them. C arrives withemailpopulated as expected.