Search documentation

Find pages, sections, and content across all docs.

WavedashDocs

Events reference

Events emitted by the Wavedash SDK

The SDK emits events when things happen — a player joins a lobby, a peer connects, the backend goes offline. Your game listens for these to react in real time.

Listening for events

WavedashSDK.lobby_joined.connect(_on_lobby_joined)
WavedashSDK.lobby_message.connect(_on_lobby_message)
WavedashSDK.p2p_connection_established.connect(_on_peer_connected)
WavedashSDK.backend_connected.connect(_on_backend_connected)
Wavedash.SDK.OnLobbyJoined += OnLobbyJoined;
Wavedash.SDK.OnLobbyMessage += OnLobbyMessage;
Wavedash.SDK.OnP2PConnectionEstablished += OnPeerConnected;
Wavedash.SDK.OnBackendConnected += OnBackendConnected;
// Strongly typed payload
// .on() returns an unsubscribe function.
const unsubscribeLobbyJoined = Wavedash.on(Wavedash.Events.LOBBY_JOINED, (payload) => {
  console.log(`Joined lobby ${payload.lobbyId}`);
});
Wavedash.on(Wavedash.Events.LOBBY_MESSAGE, (payload) => {
  console.log(`${payload.username}: ${payload.message}`);
});
Wavedash.on(Wavedash.Events.P2P_CONNECTION_ESTABLISHED, (payload) => {
  console.log(`Peer connected: ${payload.username}`);
});
Wavedash.on(Wavedash.Events.BACKEND_CONNECTED, (payload) => {
  console.log(`Backend connected (attempt ${payload.connectionCount})`);
});

// Later, to detach:
unsubscribeLobbyJoined();                            // via the unsubscribe fn
Wavedash.off(Wavedash.Events.LOBBY_JOINED, handler); // or using .off

Lobby events

EventFires whenPayload
LOBBY_JOINEDYou join or create a lobbylobbyId, hostId, users, metadata
LOBBY_USERS_UPDATEDA user joins or leaveslobbyId, userId, username, userAvatarUrl, isHost, changeType ("JOINED" or "LEFT")
LOBBY_MESSAGEA chat message arrivesmessageId, lobbyId, userId, username, message, timestamp
LOBBY_DATA_UPDATEDLobby metadata changesThe full metadata object
LOBBY_KICKEDYou're removed from the lobbylobbyId, reason ("KICKED" or "ERROR")
LOBBY_INVITEYou receive a lobby invitenotificationId, lobbyId, sender (object with _id, username, avatarUrl), _creationTime

Payload examples

LOBBY_JOINED — fires once when you create or join. users includes you. metadata is whatever the host has set with setLobbyData(); it's an empty object on a fresh lobby.

{
  "lobbyId": "j570h2k8p3m7n5q1r4s9t6v2w8x0y3z4",
  "hostId": "k7m1n3p5q7r9s2t4v6w8x0y2z4a6b8c0",
  "users": [
    {
      "userId": "k7m1n3p5q7r9s2t4v6w8x0y2z4a6b8c0",
      "username": "alice",
      "userAvatarUrl": "https://cdn.wavedash.gg/avatars/alice.png",
      "lobbyId": "j570h2k8p3m7n5q1r4s9t6v2w8x0y3z4",
      "isHost": true
    },
    {
      "userId": "n2p4q6r8s0t2v4w6x8y0z2a4b6c8d0e2",
      "username": "bob",
      "lobbyId": "j570h2k8p3m7n5q1r4s9t6v2w8x0y3z4",
      "isHost": false
    }
  ],
  "metadata": {
    "gameMode": "deathmatch",
    "mapName": "arena",
    "round": "2"
  }
}

LOBBY_USERS_UPDATED — one event per join/leave. Use changeType to branch.

{
  "userId": "n2p4q6r8s0t2v4w6x8y0z2a4b6c8d0e2",
  "username": "bob",
  "userAvatarUrl": "https://cdn.wavedash.gg/avatars/bob.png",
  "lobbyId": "j570h2k8p3m7n5q1r4s9t6v2w8x0y3z4",
  "isHost": false,
  "changeType": "JOINED"
}

LOBBY_MESSAGEtimestamp is Date.now()-style ms since epoch.

{
  "messageId": "m1n3p5q7r9s1t3v5w7x9y1z3a5b7c9d1",
  "lobbyId": "j570h2k8p3m7n5q1r4s9t6v2w8x0y3z4",
  "userId": "k7m1n3p5q7r9s2t4v6w8x0y2z4a6b8c0",
  "username": "alice",
  "message": "ready when you are",
  "timestamp": 1714296245000
}

LOBBY_DATA_UPDATED — the entire metadata object, not a diff. Values are strings or numbers.

{
  "gameMode": "deathmatch",
  "mapName": "arena",
  "round": "3",
  "scoreLimit": 10
}

LOBBY_KICKED:

{
  "lobbyId": "j570h2k8p3m7n5q1r4s9t6v2w8x0y3z4",
  "reason": "KICKED"
}

LOBBY_INVITE — note the sender object uses _id and avatarUrl (not userId/userAvatarUrl like the lobby user shape above).

{
  "notificationId": "p3q5r7s9t1v3w5x7y9z1a3b5c7d9e1f3",
  "lobbyId": "j570h2k8p3m7n5q1r4s9t6v2w8x0y3z4",
  "sender": {
    "_id": "k7m1n3p5q7r9s2t4v6w8x0y2z4a6b8c0",
    "username": "alice",
    "avatarUrl": "https://cdn.wavedash.gg/avatars/alice.png"
  },
  "_creationTime": 1714296245000
}

P2P events

EventFires whenPayload
P2P_CONNECTION_ESTABLISHEDBoth data channels to a peer are open and the peer is ready to send/receiveuserId, username
P2P_PEER_RECONNECTINGA peer's ICE connection failed and the SDK is attempting an ICE restart. Fires on both sides of the link.userId, username
P2P_PEER_RECONNECTEDA peer that was reconnecting is back online. Fires on both sides.userId, username
P2P_PEER_DISCONNECTEDA peer's data channel closed — they left the lobby, their browser tab exited, or the SDK gave up after too many failed ICE restartsuserId, username
P2P_CONNECTION_FAILEDA peer connection failed terminally (no TURN credentials, data-channel error, or ICE restart attempts exhausted)userId, username, error
P2P_PACKET_DROPPEDThe SDK dropped an outgoing or incoming P2P packet (see Packet drop reasons)channel, direction ("SEND" or "RECEIVE"), reason, droppedCount, droppedTotal

Peer connection lifecycle

A healthy peer lifecycle is P2P_CONNECTION_ESTABLISHED → (traffic) → P2P_PEER_DISCONNECTED. When the network hiccups — a Wi-Fi switch, a brief NAT rebinding — the SDK does an ICE restart and you'll see P2P_PEER_RECONNECTINGP2P_PEER_RECONNECTED in between. Treat that pair as "this peer is unreachable right now" → "go ahead and resume sending". It's still the same peer, no re-handshake is needed, and you don't need to tear down any per-peer state.

If ICE restarts exceed the SDK's retry budget (3 attempts), the SDK emits P2P_CONNECTION_FAILED and then P2P_PEER_DISCONNECTED as the channel closes. For broadcast sends, this is the signal to drop the peer from your "reachable" set — see Multiplayer networking for the full pattern.

Packet drop reasons

The reason field on P2P_PACKET_DROPPED tells you what remedy, if any, is needed:

ReasonDirectionHappens whenWhat to do
QUEUE_FULLRECEIVEThe receive-side ring buffer is fullDrain channels more often, throttle send rate, or raise p2p.maxIncomingMessages in init()
PAYLOAD_TOO_LARGEEitherPayload exceeds the configured slot sizeReduce payload or raise p2p.messageSize in init()
INVALID_PAYLOAD_SIZESENDpayloadSize was ≤ 0, larger than the buffer, or payload was nullProgramming error — fix the call site
INVALID_CHANNELEitherChannel index was outside [0, 8)programming error, SDK version skew or a malicious peer.
MALFORMEDRECEIVEIncoming wire data was too short to parse a channel headerAlmost always SDK version skew; channel is -1
PEER_NOT_READYSENDTarget peer's channel wasn't open, or P2P wasn't initialized. Only emitted for unicast sendP2PMessage; broadcasts silently skip unready peers.Wait for P2P_CONNECTION_ESTABLISHED and watch the reconnect/disconnect events for reachability

Events are rate-limited per (direction, channel, reason) tuple. The first drop on an idle tuple fires immediately; further drops within 500 ms are coalesced into a single event at the end of the window. droppedCount is the number of drops in the current event, droppedTotal is the cumulative count for that tuple since P2P init.

Payload examples

The lifecycle events all share the same shape:

{
  "userId": "n2p4q6r8s0t2v4w6x8y0z2a4b6c8d0e2",
  "username": "bob"
}

P2P_CONNECTION_FAILED adds an error string:

{
  "userId": "n2p4q6r8s0t2v4w6x8y0z2a4b6c8d0e2",
  "username": "bob",
  "error": "ICE restart attempts exhausted"
}

P2P_PACKET_DROPPED:

{
  "channel": 0,
  "direction": "SEND",
  "reason": "QUEUE_FULL",
  "droppedCount": 5,
  "droppedTotal": 47
}

Stats events

EventFires whenPayload
STATS_STOREDStats are persisted to the serversuccess, message (optional)

Payload examples

{ "success": true }

On failure, message describes what went wrong:

{ "success": false, "message": "Network error while persisting stats" }

Backend events

EventFires whenPayload
BACKEND_CONNECTEDConnected to WavedashisConnected, hasEverConnected, connectionCount, connectionRetries
BACKEND_DISCONNECTEDLost connectionSame as above
BACKEND_RECONNECTINGAttempting to reconnectSame as above

Payload examples

All three events share this shape. connectionCount increments on each successful connect; connectionRetries counts failed attempts in the current outage.

{
  "isConnected": true,
  "hasEverConnected": true,
  "connectionCount": 2,
  "connectionRetries": 0
}

Fullscreen events

EventFires whenPayload
FULLSCREEN_CHANGEDHost page enters or exits fullscreenisFullscreen (boolean)

See Fullscreen for the request/toggle API and overlay-aware integration.

Per-language names

Each event is exposed as a JS constant, a Unity C# event, and a Godot signal. Connect to whichever matches your engine — the payload fields are the same across all three.

EventJavaScript constantUnity eventGodot signal
Lobby joinedWavedash.Events.LOBBY_JOINEDOnLobbyJoinedlobby_joined
Lobby users updatedWavedash.Events.LOBBY_USERS_UPDATEDOnLobbyUsersUpdatedlobby_users_updated
Lobby messageWavedash.Events.LOBBY_MESSAGEOnLobbyMessagelobby_message
Lobby data updatedWavedash.Events.LOBBY_DATA_UPDATEDOnLobbyDataUpdatedlobby_data_updated
Lobby kickedWavedash.Events.LOBBY_KICKEDOnLobbyKickedlobby_kicked
Lobby inviteWavedash.Events.LOBBY_INVITEOnLobbyInvitelobby_invite
P2P connection establishedWavedash.Events.P2P_CONNECTION_ESTABLISHEDOnP2PConnectionEstablishedp2p_connection_established
P2P peer reconnectingWavedash.Events.P2P_PEER_RECONNECTINGOnP2PPeerReconnectingp2p_peer_reconnecting
P2P peer reconnectedWavedash.Events.P2P_PEER_RECONNECTEDOnP2PPeerReconnectedp2p_peer_reconnected
P2P peer disconnectedWavedash.Events.P2P_PEER_DISCONNECTEDOnP2PPeerDisconnectedp2p_peer_disconnected
P2P connection failedWavedash.Events.P2P_CONNECTION_FAILEDOnP2PConnectionFailedp2p_connection_failed
P2P packet droppedWavedash.Events.P2P_PACKET_DROPPEDOnP2PPacketDroppedp2p_packet_dropped
Current stats receivedOnCurrentStatsReceivedcurrent_stats_received
Stats storedWavedash.Events.STATS_STOREDOnStatsStoredstats_stored
Backend connectedWavedash.Events.BACKEND_CONNECTEDOnBackendConnectedbackend_connected
Backend disconnectedWavedash.Events.BACKEND_DISCONNECTEDOnBackendDisconnectedbackend_disconnected
Backend reconnectingWavedash.Events.BACKEND_RECONNECTINGOnBackendReconnectingbackend_reconnecting
Fullscreen changedWavedash.Events.FULLSCREEN_CHANGEDOnFullscreenChangedfullscreen_changed

Current stats received is surfaced as the awaited response of requestStats() in JavaScript (no separate event). Unity and Godot raise it as a callback instead because their bindings deliver stats asynchronously through the SDK's engine-callback bridge.

Godot additionally emits per-call signals (got_leaderboard, got_leaderboard_entries, posted_leaderboard_score, ugc_item_created, ugc_item_updated, ugc_item_downloaded, remote_file_uploaded, remote_file_downloaded, remote_directory_downloaded, got_remote_directory_listing, got_lobbies, sent_lobby_invite, got_lobby_invite_link, lobby_created, lobby_left, got_friends, got_user_jwt, user_avatar_loaded). These fire with the response of the matching async call — use them if you prefer signal-based flow over await.

Deferring events

If your game needs time to load before handling events, pass deferEvents: true to init(). The SDK queues all events until you call readyForEvents().

func _ready():
    WavedashSDK.init({"deferEvents": true})

    # set up listeners, load assets, etc.

    WavedashSDK.ready_for_events()  # flush the queue
using System.Collections.Generic;

void Awake()
{
    Wavedash.SDK.Init(new Dictionary<string, object>
    {
        { "deferEvents", true }
    });

    // set up listeners, load assets, etc.

    Wavedash.SDK.ReadyForEvents(); // flush the queue
}
Wavedash.init({ deferEvents: true });

// set up listeners, load assets, etc.

Wavedash.readyForEvents(); // flush the queue