> ## Documentation Index
> Fetch the complete documentation index at: https://docs.playflowcloud.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Lobby Events

> React to lobby and player state changes by subscribing to events from the PlayFlowLobbyManagerV2.

The `PlayFlowLobbyManagerV2` uses a system of `UnityEvent`s to communicate state changes and important occurrences to your game. By subscribing to these events, you can create a responsive UI and trigger game logic at the right moments.

All events are accessible through the `PlayFlowLobbyManagerV2.Instance.Events` property, which is an instance of `PlayFlowEvents`.

<Info>
  The `V2` in `PlayFlowLobbyManagerV2` is the SDK component version and is unrelated to the REST API version. Lobbies and matchmaking are served exclusively by the v3 engine API (`/api/v3/lobbies`); there is no v2 compatibility layer for them. These `UnityEvent`s are the SDK's abstraction over the server's real-time SSE event stream — you subscribe to C# events instead of parsing the raw stream yourself.
</Info>

## Prerequisites

Before you wire up events, make sure you have:

* A lobby configuration and the `PlayFlowLobbyManagerV2` set up in your scene — see [Lobbies](/unity/lobbies).
* Matchmaking modes (and optional match confirmation) configured if you use the matchmaking events — see [Matchmaking](/unity/matchmaking).

## Subscribing to Events

You subscribe to events using the standard `AddListener` method. It's best practice to do this after the manager has been initialized.

```csharp theme={null}
using PlayFlow;
using UnityEngine;

public class MyLobbyUI : MonoBehaviour
{
    void Start()
    {
        // It's good practice to wait until the manager is ready
        PlayFlowLobbyManagerV2.Instance.Initialize("my-player-id", SubscribeToEvents);
    }

    void SubscribeToEvents()
    {
        var events = PlayFlowLobbyManagerV2.Instance.Events;
    
        // Fired when the local player joins a lobby
        events.OnLobbyJoined.AddListener(OnLobbyJoined);
    
        // Fired when another player joins the lobby
        events.OnPlayerJoined.AddListener(OnPlayerJoined);

        // Fired when a player leaves
        events.OnPlayerLeft.AddListener(OnPlayerLeft);
    
        // Fired when the lobby data is updated
        events.OnLobbyUpdated.AddListener(OnLobbyUpdated);

        // Fired on connection errors
        events.OnError.AddListener(OnError);
    }

    void OnLobbyJoined(Lobby lobby)
    {
        Debug.Log($"Successfully joined lobby: {lobby.name}");
        // Update UI to show lobby screen
    }

    void OnPlayerJoined(PlayerAction action)
    {
        Debug.Log($"Player {action.PlayerId} joined the lobby!");
        // Add player to UI list
    }

    void OnPlayerLeft(PlayerAction action)
    {
        Debug.Log($"Player {action.PlayerId} left the lobby.");
        // Remove player from UI list
    }

    void OnLobbyUpdated(Lobby lobby)
    {
        Debug.Log("Lobby data has been updated.");
        // Refresh the entire lobby UI
    }

    void OnError(string errorMessage)
    {
        Debug.LogError($"A PlayFlow error occurred: {errorMessage}");
        // Show an error popup to the user
    }

    void OnDisable()
    {
        // Don't forget to unsubscribe to prevent memory leaks!
        var events = PlayFlowLobbyManagerV2.Instance.Events;
        if (events != null)
        {
            events.OnLobbyJoined.RemoveListener(OnLobbyJoined);
            events.OnPlayerJoined.RemoveListener(OnPlayerJoined);
            events.OnPlayerLeft.RemoveListener(OnPlayerLeft);
            events.OnLobbyUpdated.RemoveListener(OnLobbyUpdated);
            events.OnError.RemoveListener(OnError);
        }
    }
}
```

## Event Reference

Here is a complete list of events available on `PlayFlowEvents`.

### Lobby Events

<ResponseField name="OnLobbyCreated" type="LobbyEvent" required>
  Fired when a lobby is successfully created by the local player. The event passes the created `Lobby` object.
  *Argument: `Lobby`*
</ResponseField>

<ResponseField name="OnLobbyJoined" type="LobbyEvent" required>
  Fired when the local player successfully joins a lobby.
  *Argument: `Lobby`*
</ResponseField>

<ResponseField name="OnLobbyUpdated" type="LobbyEvent" required>
  Fired whenever the `Lobby` data changes. This can be due to players joining/leaving, player state changes, or host actions.
  *Argument: `Lobby`*
</ResponseField>

<ResponseField name="OnLobbyLeft" type="VoidEvent" required>
  Fired after the local player successfully leaves a lobby.
  *Argument: `void`*
</ResponseField>

### Match Events

<ResponseField name="OnMatchStarted" type="LobbyEvent" required>
  Fired for all players in a lobby when the host starts the match (via `StartMatch()` or a completed matchmaking cycle). Status is now `in_game`, but the server is still `launching`.
  *Argument: `Lobby`*
</ResponseField>

<ResponseField name="OnMatchRunning" type="ConnectionInfoEvent" required>
  Fired when the game server has finished booting and is ready to accept connections. The payload is a quick-access `ConnectionInfo` for the first port. For multi-port games use `lobby.TryGetPort("name", out var port)` instead.
  *Argument: `ConnectionInfo`*
</ResponseField>

<ResponseField name="OnMatchServerDetailsReady" type="PortMappingInfoListEvent" required>
  Fires alongside `OnMatchRunning` with the **full list of ports** (name, protocol, host, internal/external port, TLS state). Subscribe to this when your game has more than one port (e.g. `game_udp` + `voice_tcp`).
  *Argument: `List<PortMappingInfo>`*
</ResponseField>

<ResponseField name="OnMatchEnded" type="LobbyEvent" required>
  Fired for all players when a match ends — either the host called `EndMatch()`, or the server stopped on its own (TTL, crash, clean exit). The lobby auto-heals back to `waiting` with the same players and invite code, ready for a rematch.
  *Argument: `Lobby`*
</ResponseField>

### Matchmaking Events

<ResponseField name="OnMatchmakingStarted" type="LobbyEvent" required>
  Fired when the lobby enters the matchmaking queue (after a successful `FindMatch()` call). Status is now `in_queue`.
  *Argument: `Lobby`*
</ResponseField>

<ResponseField name="OnQueueStats" type="QueueStatsEvent" required>
  Fired roughly every 10 seconds while the lobby is searching, with live telemetry: total players searching, number of lobbies in queue, and average wait time. Perfect for a "42 players searching · \~12s wait" UI.
  *Argument: `QueueStats`*
</ResponseField>

<ResponseField name="OnMatchFound" type="LobbyEvent" required>
  Fired when opponents are found and the match is committed. For modes **without** confirmation this fires right before `OnMatchStarted`. For modes **with** confirmation enabled, use `OnMatchAwaitingConfirmation` instead — `OnMatchFound` only fires after every lobby has accepted.
  *Argument: `Lobby`*
</ResponseField>

<ResponseField name="OnMatchAwaitingConfirmation" type="LobbyEvent" required>
  Only relevant when the matchmaking mode has `matchConfirmation.enabled: true` in the dashboard. Fired when a match proposal is found — show a CS2-style "Accept Match" dialog. The lobby is in `match_found` status. Use `lobby.matchmaking.confirmation.deadline` (ISO 8601 timestamp) for the countdown.
  *Argument: `Lobby`*
</ResponseField>

<ResponseField name="OnMatchConfirmed" type="LobbyEvent" required>
  Fired after `ConfirmMatch()` succeeds — your lobby has accepted. You're still waiting on other lobbies to confirm. Update your UI to show "Waiting on N other players…".
  *Argument: `Lobby`*
</ResponseField>

<ResponseField name="OnMatchDeclined" type="LobbyEvent" required>
  Fired when the match proposal is cancelled — either someone called `DeclineMatch()`, or the confirmation window expired. All participating lobbies return to `waiting`. Show a "Match declined — click to re-queue" UI.
  *Argument: `Lobby`*
</ResponseField>

<ResponseField name="OnMatchmakingCancelled" type="LobbyEvent" required>
  Fired when the host calls `CancelMatchmaking()` and the lobby returns to `waiting` voluntarily.
  *Argument: `Lobby`*
</ResponseField>

<ResponseField name="OnMatchmakingTimeout" type="LobbyEvent" required>
  Fired when the matchmaking queue times out without finding a match. The lobby returns to `waiting`.
  *Argument: `Lobby`*
</ResponseField>

### Player Events

<ResponseField name="OnPlayerJoined" type="PlayerEvent" required>
  Fired for all players in a lobby when a new player joins. The `PlayerAction` contains the ID of the player who joined.
  *Argument: `PlayerAction`*
</ResponseField>

<ResponseField name="OnPlayerLeft" type="PlayerEvent" required>
  Fired for all players in a lobby when another player leaves. The `PlayerAction` contains the ID of the player who left.
  *Argument: `PlayerAction`*
</ResponseField>

### System Events

<ResponseField name="OnConnected" type="VoidEvent" required>
  Fired when the client successfully connects to the PlayFlow service.
  *Argument: `void`*
</ResponseField>

<ResponseField name="OnDisconnected" type="VoidEvent" required>
  Fired when the client disconnects from the PlayFlow service.
  *Argument: `void`*
</ResponseField>

<ResponseField name="OnError" type="StringEvent" required>
  Fired when any error occurs within the SDK. Provides a string message describing the error.
  *Argument: `string`*
</ResponseField>

### State Events

<ResponseField name="OnStateChanged" type="StateChangedEvent" required>
  Fired whenever the manager's internal state transitions (`Disconnected`, `Connecting`, `Connected`, `InLobby`, `InMatch`, etc.). Useful for gating UI flow — `(oldState, newState)`.
  *Argument: `LobbyState`, `LobbyState`*
</ResponseField>
