Webinar mode: setExternalShareSource(share, share_audio) returns UNKNOWN(13) even with app_privilege_token + raw recording active (works in meeting)

Summary

In a webinar, IZoomSDKShareSourceHelper::setExternalShareSource(share, share_audio)
returns SDKERR_UNKNOWN(13) while the exact same code returns SUCCESS(0) in
a regular meeting. Share-video registration works in both; only the share-audio
side is rejected in webinar. I have ruled out every account/permission/timing
hypothesis I can think of and am out of ideas — looking for guidance on whether
this is intentional or a bug, and what the supported path is for injecting
computer-sound audio into a webinar via the raw data API.

Environment

  • Zoom Meeting SDK for Windows: 7.0.2.34512
  • OS: Windows 11 Pro 26200
  • App type: General App (User-managed), Development credentials
    • Selected Zoom products: Meetings + Webinars
    • Embed > Meeting SDK enabled
    • “Are you developing a programmatic join use case?” = ON
  • S2S OAuth scopes:
    • user:read:zak:admin
    • meeting:read:local_recording_token:admin
    • webinar:read:local_recording_token:admin
  • Account: Pro (with webinar add-on)
  • Account-level recording: Local + Cloud both ON
  • Webinar-level “Allow local recording”: ON
  • Webinar is owned by the same user the bot joins as (ZAK userId=me)

What I am trying to do

A headless C++ bot that streams a pre-recorded MP4 into a Zoom session via the
raw data API, with four channels:

  • IZoomSDKVideoSource — camera tile
  • IZoomSDKVirtualAudioMicEvent — mic (speaker voice)
  • IZoomSDKShareSource — share video (slides)
  • IZoomSDKShareAudioSource — share audio (BGM, full-bandwidth music)

Problem: meeting works, webinar does not

Meeting (meetingId 3021611089) — works:

MySelf: role=USERROLE_HOST(1) isHost=true
HasRawdataLicense() (initial) -> false
CanStartRawRecording() -> WRONG_USAGE
EnableShareComputerSound(true) -> SUCCESS(0)
setExternalShareSource(share, share_audio) -> SUCCESS(0)  ✅
[ShareSource]      onStartSend     ✅
[ShareAudioSource] onStartSendAudio ✅
sendShareAudio(...) -> SUCCESS for every chunk

Note that CanStartRawRecording returns WRONG_USAGE here too, yet
setExternalShareSource works — so the “raw recording” gate by itself
does not seem to be what controls share-audio registration in meeting mode.

Webinar (webinarId 84790922291) — fails:

MySelf: role=USERROLE_HOST(1) isHost=true
HasRawdataLicense() (initial) -> false
CanStartRawRecording() -> WRONG_USAGE
EnableShareComputerSound(true) -> SUCCESS(0)
setExternalShareSource(share, share_audio) -> UNKNOWN(13)  ❌
[ShareSource]      onStartSend       ✅ (share video alive)
[ShareAudioSource] onStartSendAudio  ❌ (never fires)

Things I have already ruled out

  1. Role: Tested with isHost=true, USERROLE_HOST. Same result with COHOST.
  2. Permissions / settings:
    • Account Cloud + Local recording: ON
    • Webinar Local recording: ON
    • “Webinars” selected in app’s “Select where to use your app” panel
    • “Programmatic join use case” toggle: ON
  3. Manual sanity check: From the Zoom desktop client, the SAME user account
    in the SAME webinar can manually do “Share screen → Share computer sound”
    successfully. So the account license and webinar settings are sufficient.
    It is specifically the SDK raw-data injection that fails.
  4. Timing / async-init race: Tested with a 15-second sleep between
    MEETING_STATUS_INMEETING and the setExternalShareSource call. No effect.
  5. Mic stream collision: Disabled mic source entirely (no
    setExternalAudioSource, no JoinVoip, no Mute/UnMute dance). Same
    UNKNOWN(13).
  6. EnableShareComputerSound(true) before the helper call: in place
    (returns SUCCESS). No effect on the subsequent UNKNOWN(13).

Things I have additionally tried per Zoom’s “Use raw data” doc

Per the Zoom Meeting SDK docs section “Receive local recording permission
through an OAuth app”, I implemented the app_privilege_token flow:

  1. Added scopes meeting:read:local_recording_token:admin and
    webinar:read:local_recording_token:admin to my S2S app.
  2. Fetched the token via GET /v2/webinars/{id}/jointoken/local_recording.
    Response: {"token":"<9-char string>","expire_in":120} — successfully returned.
  3. Passed it as JoinParam.withoutloginuserJoin.app_privilege_token alongside
    userZAK.

Observed effect:

  • [SDK] onRecordingStatus: Recording_Start(0) fires automatically shortly
    after MEETING_STATUS_INMEETING — so the SDK considers raw recording started.
  • CanStartRawRecording() and (forced) StartRawRecording() both still
    return WRONG_USAGE(2) — presumably “already started”.
  • Despite raw recording being active, setExternalShareSource(share, share_audio)
    still returns UNKNOWN(13).
  • No “Recording consent” dialog appears for the other webinar participants,
    which the docs say should happen when StartRawRecording succeeds. So it
    looks like the SDK-side state says “recording started” but the server-side
    may not be in sync.

Workaround that partially works

setExternalShareSource(share, nullptr) + setSharePureAudioSource(share_audio)
in webinar:

  • Both calls return SUCCESS(0)
  • [ShareAudioSource] onStartSendAudio fires
  • sendShareAudio succeeds, audio actually reaches participants

BUT: as soon as setSharePureAudioSource is called, the share-video session is
killed ([ShareSource] onStartSend immediately followed by onStopSend). So
share-video and share-audio appear to be mutually exclusive when using
setSharePureAudioSource. Same exclusion observed on Linux SDK 7.0.2.5001.

Questions

  1. Is IZoomSDKShareSourceHelper::setExternalShareSource(share, share_audio)
    officially supported in webinar mode? The header (rawdata_share_source_helper_interface.h)
    doesn’t gate it, but the published docs only mention the single-argument
    form. If unsupported, can the header please be annotated to make this clear?
  2. If supported: what additional configuration enables it? My ruled-out list
    above covers everything I could find — is there another flag, marketplace
    feature add-on, or webinar setting I’m missing?
  3. The fact that onRecordingStatus(Recording_Start) fires but no consent
    dialog appears for participants
    seems like a bug or a real signal that
    the server hasn’t actually opened the raw-data pipeline. Is there a way to
    verify server-side raw recording state from the SDK?
  4. setSharePureAudioSource works in webinar but kills share-video. Is the
    share-video / pure-audio exclusion an intentional SDK design, and is there
    any way to dynamically toggle between them at runtime (e.g., share-video
    for slide segments, pure-audio for BGM segments) without breaking the
    session?

Attached

Encrypted SDK logs available — happy to share via DM. Timestamps:

  • Failing webinar call: 2026-05-18T07:39:13 JST
  • Successful pure-audio webinar call: 2026-05-18T06:45:03 JST
  • Successful meeting call (reference): 2026-05-18T05:52:58 JST

Account ID and SDK app credentials available via DM if useful.

Thanks in advance for any insight.

1 Like