ZoomVideoSDK iOS Whiteboard - Collaboration server handshake failed

I’m using the ZoomVideoSDK and trying to implement the whiteboard feature. I’ve noticed that most of the time, the users can both join the whiteboard on the first attempt. However, if one of them leave the whiteboard, or a new whiteboard is started, the other user is not able to join. Lots of error messages with the format:

Collaboration server handshake failed - s-code-d05cca9618-1771363688026

Relavent Code:

//MARK: WhiteBoard

var whiteBoardHelper: ZoomVideoSDKWhiteboardHelper? {
    return ZoomVideoSDK.shareInstance()?.getWhiteboardHelper()
}

@objc func showWhiteboardPressed() {
    print("whiteBoardHelper?.canStartWhiteboard: \(whiteBoardHelper?.canStartShareWhiteboard())")
    let canStartWhiteboard = whiteBoardHelper?.canStartShareWhiteboard() ?? false
    if canStartWhiteboard {
        let result = whiteBoardHelper?.startShareWhiteboard()
        print("result share whiteboard: \(result?.rawValue)")
        if result?.rawValue == 0 {
            presentWhiteBoardVC(shouldStart: true)
        } else {
            if let error = result {
                self.showError(error)
            } else {
                let alert = UIAlertController(title: "Oops!", message: "We were unable to start a whiteboard because of an unknown error. Please try again.", preferredStyle: .alert)
                alert.addAction(UIAlertAction(title: "Ok", style: .cancel, handler: { action in
                }))
                self.present(alert, animated: true)
            }
            
        }
    } else {
        let alert = UIAlertController(title: "Oops!", message: "You are unable to start a whiteboard, as you do not have the correct privelages.", preferredStyle: .alert)
        alert.addAction(UIAlertAction(title: "Ok", style: .cancel, handler: { action in
        }))
        self.present(alert, animated: true)
    }
}

func presentWhiteBoardVC(shouldStart: Bool = false) {
    whiteBoardVC = WhiteBoardViewController()
    
    whiteBoardVC?.shouldStart = shouldStart
    whiteBoardVC?.onExit = {

// self.whiteBoardHelper?.stopShareWhiteboard()
// self.whiteBoardHelper?.unSubscribeWhiteboard()
self.whiteBoardVC = nil
}
let nav = UINavigationController(rootViewController: whiteBoardVC!)
nav.modalPresentationStyle = .fullScreen
self.present(nav, animated: true) {

    }
}

func onUserWhiteboardShareStatusChanged(_ user: ZoomVideoSDKUser, whiteboardhelper whiteboardHelper: ZoomVideoSDKWhiteboardHelper) {
    switch user.getWhiteboardStatus() {
    case .started:
        // A user has started sharing a whiteboard. Subscribe to display it.
        print("\(Date()): \(String(describing: Swift.type(of: self))), \(#function), \(#line)")
        if user.getID() != getMySelf?.getID() {
            if whiteBoardVC == nil {
                presentWhiteBoardVC()
            }
        }
    case .stopped:
        // The whiteboard sharing has ended. Unsubscribe to remove the view.
        print("\(Date()): \(String(describing: Swift.type(of: self))), \(#function), \(#line)")
        if user.getID() != getMySelf?.getID() {
            whiteboardHelper.unSubscribeWhiteboard()
            whiteBoardVC?.dismiss(animated: true)
            whiteBoardVC = nil
        }
    @unknown default:
        print("unknown whiteboard status")
    }
}

import UIKit

import ZoomVideoSDK
import ZoomVideoSDK.ZoomVideoSDKWhiteboardHelper

class WhiteBoardViewController: UIViewController {

var shouldStart = false
var onExit : (() -> ())? = { }

override func viewWillAppear(_ animated: Bool) {
    super.viewWillAppear(animated)
    self.view.backgroundColor = .white
    self.navigationItem.leftBarButtonItem = UIBarButtonItem(barButtonSystemItem: .stop, target: self, action: #selector(exit))
    self.title = "Whiteboard"
    self.whiteBoardHelper?.subscribeWhiteboard(self)
}

@objc func exit() {
    self.onExit?()
    if shouldStart {
        whiteBoardHelper?.stopShareWhiteboard()
    }
    whiteBoardHelper?.unSubscribeWhiteboard()
    self.dismiss(animated: true)
}

var whiteBoardHelper: ZoomVideoSDKWhiteboardHelper? {
    return ZoomVideoSDK.shareInstance()?.getWhiteboardHelper()
}

}

I’m also hit with exactly the same issue in my app (mixing native iOS and web clients), and after several weeks of debugging I’m stuck.

Setup: the session is always created by the web user. Because when iOS is the one who creates the room — no problem. The bug only happens when a web participant starts and stops the whiteboard while iOS is a guest(role_type 1 as co-host).

What the logs show
Web user starts whiteboard → we get the delegate callback, open the VC, subscribe — works:

onUserWhiteboardShareStatusChanged: user=shacharadmin started=YES active=YES
Remote whiteboard started — first open
viewDidAppear: subviews=2 frame={{0, 0}, {430, 932}}
attemptSubscribe(viewDidAppear): rawValue=0 retry=0 subviews=1

Web user stops it → we dismiss and unsubscribe:

onUserWhiteboardShareStatusChanged: user=shacharadmin started=NO active=NO
Remote whiteboard stopped — dismissing
unsubscribeWhiteboard: result=ZoomVideoSDKError(rawValue: 0)
Error acquiring assertion: <RBSAssertionErrorDomain Code=2 “Specified target process 3017 does not exist”>
ProcessAssertion::acquireSync Failed to acquire RBS assertion ‘XPCConnectionTerminationWatchdog’ for PID=3017

So the WKWebView’s WebContent process (3017) is already dead at this point. That’s the stale state.
Web user restarts → the delegate fires again, but every attempt to open the whiteboard produces a WebKit stack trace immediately on onUserWhiteboardShareStatusChanged:

onUserWhiteboardShareStatusChanged: user=admin started=YES active=YES
Remote whiteboard restarted
1  WebKit  0x1a1190584 …
2  WebKit  0x1a119173c …
3  WebKit  0x1a11c5c84 …
4  WebKit  0x1a11c4790 …
5  WebKit  0x1a0c66b8c …
JavaScriptCore …
CoreFoundation …

This stack trace comes from inside the SDK on every whiteboard status change after the first stop — before we even call subscribeWhiteboard. The WKWebView is already broken at the delegate level.

Then if we try to subscribe anyway:

preWarmWhiteboard: already active
startWhiteboard: whiteboard already active, will subscribe only
viewDidAppear: subviews=2 frame={{0, 0}, {430, 932}}
attemptSubscribe(viewDidAppear): rawValue=0 retry=0 subviews=1

Returns rawValue=0 (success) but whiteboard never renders. And we can’t pre-warm to reset because we’re not the host:

preWarmWhiteboard: cannot start (not host?)
startWhiteboard: canStartShareWhiteboard=false
Whiteboard start failed: Whiteboard is not available

Root cause I identified

The SDK’s internal WKWebView (the one backing the whiteboard collaboration layer) retains stale state after the first unsubscribeWhiteboard(). On the second subscribe attempt it tries to reconnect to the collaboration server with the old session tokens/handshake data, which the server rejects.

Concretely:

subscribeWhiteboard returns .Errors_Success on the second attempt — so the API does not signal failure
But the whiteboard view never renders; the web console equivalent logs Collaboration server handshake failed - s-code-…
The only reliable way to get a clean state is to restart the entire Zoom session (leave and rejoin), which re-creates the WKWebView from scratch

iOS Video SDK 2.5.5(2.5.7)
Xcode 26.0
Iphone/Ipad 26.0.1(26.4)

I ended up restarting the session on a 2 second timer any time either user joins the whiteboard, and while it was painful to implement, I got it working with that work around. A response here would be great!