participant_joined: identifying SDK vs browser clients when email and id are empty

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:

  1. Browser/web-client joiners who aren’t signed in
  2. Notetaker bots that use the Meeting SDK without registering a Marketplace app
  3. Anonymous guests joining via meeting link

Two specific questions:

  1. Is there any field in the participant_joined payload (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.
  2. participant_uuid appears 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:

  1. Subscribe a Webhook-only app to the meeting.participant_joined event.
  2. Authentication: Server-to-Server OAuth app with the meeting:read:admin scope (also reproduces with a Webhook-only app using the event subscription alone).
  3. 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
  4. Inspect the three participant_joined payloads. In our testing, A and B both arrive with email empty, id empty, participant_uuid session-scoped, with no field we’ve found that distinguishes them. C arrives with email populated as expected.

No, not reliably from meeting.participant_joined alone. Zoom documents the meeting.participant_joined participant object with identifiers like email, user_id, id, participant_user_id, participant_uuid, registrant_id, and customer_key, but not a general client-type, browser-vs-SDK flag, SDK app ID, or Marketplace app identifier. The only SDK-specific-looking field is customer_key which Zoom describes as the participant’s SDK identifier, so it can help only when the joining client actually provides or surfaces that value.

For the empty email and id case, Zoom’s behavior matches what you’re seeing. Zoom says email is returned only when the participant joined by logging in, subject to email address display rules, and participant_user_id returns an empty string when the participant joins without logging in. So anonymous browser guests and anonymous SDK joiners can collapse into the same observable shape.

I see, and what about the participant_uuid appearing to be session scoped? Is there a unique identifier for a participant that does not change per join and remains the same throughout the scope of the meeting?