Ping in this context likely refers to the RFC6455/WebSocket Protocol defined “ping” control message, rather than the more generic concept of a message used as a liveness check.
You are correct in that any application level websocket subprotocol can define a particular message format as a ping/liveness check without the cooperation of the underlying protocols or libraries.
The WebSocket Protocol level ping/pong messages however, work a little differently and their behavior cannot be replicated via an application level subprotocol using data messages. Specifically, because the WS ping message is a control message rather than a data message it does not show up or affect the application level stream of messages. It won’t trigger onmessage in browsers or the message handler in WebSocket++ (most other WebSocket libraries are similar). This allows a server or proxy to perform a live-ness check on a connection without worrying about confusing the application level subprotocol.
Additionally, control messages can be interleaved with data messages. If one endpoint is sending a very long message or just sending one very slowly and it fragments the message appropriately the two endpoints can perform the ping/pong steps during receipt of the data message without corrupting it.
Some WebSocket libraries/APIs (particularly those in browsers) do not expose the protocol level control messages to their API. WebSocket++, as it is designed to be able to be used in lower level contexts, does provide handlers for being notified about and changing the default behavior of these control messages. This behavior is the same for both clients and servers. Generally though, servers care more about things like disconnecting broken clients and so much of the literature & documentation is focused on that use case.