Errors as data, not exceptions
An error message in your protocol is just another message: typed envelope, machine-readable code, human-readable text. The client UI maps codes to behaviors (toast, modal, redirect). Your error format is part of your public API — pick it once and stick to it.
Six standard codes
Cover these six and you have most of REST's error vocabulary translated to WebSocket: invalid_message, unauthorized, forbidden, not_found, rate_limited, internal_error. Add app-specific codes as needed (room_full, session_expired, etc.) but keep them stable across releases.