Get players into a session

You need: matchmaking, multiplayer and auth composed.

Multiplayer gives you a session once players are in it. This is how they get there.

Which one to reach for

Four approaches is the model behind this page, the waiting queue is the one with the most machinery, Using Matchmaking is the API, and shipping a turn-based game is what happens after they are seated.

SituationPath
Two people in a room togetherRoom code
One person wants to play a specific personDirect invite
A recurring opponentFriends
Nobody in particularQueue

Room code — the zero-discovery path

POST /matchmaking/games/duel/rooms
→ { "code": "WXYZ-1234", "sessionId": "…" }

A session is minted with the host already in it. The code points at it.

POST /matchmaking/rooms/WXYZ-1234/join

Join tolerates lowercase, whitespace and a missing dash, because somebody is reading it aloud across a table. The alphabet already excludes I, O, 0 and 1 so nobody reads one back wrong.

Codes expire (15 minutes by default) and are limited-use (nine by default).

Direct invite

POST /matchmaking/games/duel/invites
{ "email": "ada@example.com" }

Email is the reliable key — it is unique on the user table.

A screen name is best-effort. Auth exposes no unique one, so a name matching zero users or several is refused rather than guessed — matchmaking/user_not_found.

POST /matchmaking/invites/<id>/accept   → mints the session and seats both
POST /matchmaking/invites/<id>/decline
GET  /matchmaking/invites               → pending, for the caller

Exactly two players. A game with a larger roster is refused here and told to use a code or the queue.

Friends

A symmetric graph formed by mutual accept:

POST   /matchmaking/friends/<userId>/request
POST   /matchmaking/friends/<userId>/accept
POST   /matchmaking/friends/<userId>/decline
DELETE /matchmaking/friends/<userId>
GET    /matchmaking/friends

friends: false and these never mount.

Queue

POST   /matchmaking/games/duel/queue
GET    /matchmaking/games/duel/queue
DELETE /matchmaking/games/duel/queue

A match forms on enqueue when an opponent is already waiting. The sweep exists to widen skill bands, not to pair.

queue: { initialBand: 100, widenPerSecond: 50, maxWaitSeconds: 120, sweepSeconds: 5 }

The band grows every second waited, and after maxWaitSeconds any opponent in the region qualifies.

Read what a waiting queue costs before production. sweepSeconds is the one setting that decides the bill.

Tell the player they matched

Through presence, not by polling:

GET /matchmaking/presence      (WebSocket)
{ type: "match_found";    sessionId: string; gameKey: string }
{ type: "invite";         inviteId: string;  gameKey: string; from: string }
{ type: "friend_request"; from: string }

Over the Hibernation API, so a socket waiting on nothing bills no duration. On connect it also delivers pending invites and which friends are online.

Nothing here trusts the client

Every route binds to the authenticated user id, never a client-supplied one.

A room a stranger can open in your name, or a friend request from an id the client chose, is the whole attack surface of a pairing layer.

ESC