# Ticket lifecycle

How matchmaking sessions, heartbeats, cancellation, and match delivery work, from ticket creation to a terminal result.

Source: https://birdhouse.tech/docs/lifecycle/

A ticket represents one party's search. A Roblox SDK session manages that ticket from local creation through a terminal result. Start a new session for a new search.

## Session states

| State | What it means | Terminal |
| --- | --- | --- |
| `pending` | The session exists locally, but creation has not been confirmed. | No |
| `matching` | The service accepted the ticket and the search is active. | No |
| `canceling` | Cancellation was requested; a match may still win. | No |
| `matched` | A valid match is retained in `session.Match`. | Yes |
| `canceled` | An unsent search was discarded, or cancellation/removal was confirmed. | Yes |
| `failed` | The search ended with a reason in `session.Error`. | Yes |

`TicketId` is assigned before the first request. Its presence does not mean the service accepted the ticket. Read `session.State` for the initial value; there is no initial `StateChanged("pending")` event.

## Heartbeats and retention

While sessions are live, the SDK batches outstanding work into requests of at most 100 actions. Its default heartbeat interval is **5 seconds**. The supervisor stops when no unfinished sessions remain and starts again when you create another session.

A waiting ticket expires **5 minutes** after creation or its last successful confirmation. Keep confirming while searching. A published match and its member tickets remain available for **10 minutes after publication**.

Confirming or canceling a matched ticket returns the match without extending this fixed retention window. After expiry, the API returns `not_found`.

## Match delivery

On a valid match, the SDK:

1. Stores the match in `session.Match` and sets the state to `matched`.
2. Fires `session.Matched(match)`.
3. Fires terminal `session.StateChanged("matched")`.
4. Disconnects the session's signals.

Event callbacks may yield; the SDK does not wait for one callback to finish before emitting the next event. Connect listeners immediately after `CreateSession` and use retained properties when inspecting a session later.

No acknowledgement is required or supported. A terminal matched session sends no further requests. If a handler errors, the SDK does not replay the event; your game can retry its handoff using the retained match.

> The retained outcome lives as long as your game retains the session. It is not durable storage and does not replace your game's handoff recovery strategy.

## Cancellation races

`session:Cancel()` can be called repeatedly. Before network submission begins, it finishes the session locally without creating a ticket. After submission begins, it immediately changes the local state to `canceling` and schedules cancellation on the heartbeat.

The service checks cancellation against the current ticket state. If a match has already been published, cancellation returns the match. Keep the session registered and keep processing `Matched` until a terminal outcome.

The raw API's `canceling` response means the service accepted cancellation; the SDK presents that outcome as terminal `canceled`. Do not confuse the raw API state with the SDK's local, nonterminal `canceling` state.

## Uncertain creation

The SDK generates a ticket GUID before submission. If a create response is lost or malformed, it confirms that known ID on later heartbeats instead of blindly creating another ticket.

If reconciliation cannot find the ticket, the session fails with `creation_unresolved`. This does not prove that the original request never completed: a slow original request could still finish later. Do not automatically launch a replacement search without accounting for potentially outstanding work.

## Party changes and shutdown

Options are copied at creation. Changing the original players, metadata, or rules does not update a live ticket. Cancel the existing session and wait for the terminal outcome before starting a changed search.

Your game owns party registries, `Players.PlayerRemoving`, and prevention of overlapping sessions. The SDK does not deduplicate separate sessions or clients by player ID.

`client:Destroy()` fails active sessions with `client_destroyed` and stops new requests. An in-flight request cannot be revoked; remote tickets may remain matchable until cancellation or expiry. Use destruction for shutdown or intentional abandonment, and cancellation for normal queue exits.
