Implementing In Your Game
With a FIFO 2v2 matchmaker published (see Creating a Matchmaker), your backend wires up to it through three ticket calls: create a ticket, poll for the assigned server, and cancel if the player stops searching. This page follows that FIFO flow. The examples are TypeScript, matching the Colyseus example, but the API is plain REST and works from any language.
Backend-driven matchmaking
Your backend holds the GameFlow API key and makes every ticket call. The game client only asks your backend to "find a match" and waits for a server. Keeping the key on the backend means it never ships to players.
Game Client ──► Your Backend ──► GameFlow ticket API
"find match" holds API key create / poll / cancel
All calls go to the GameFlow API base URL and send your key in the X-Api-Key header.
1. Create a ticket
When a player starts searching, enqueue one ticket.
const res = await fetch(`${GAMEFLOW_API_URL}/matchmaking/tickets`, {
method: "POST",
headers: { "X-Api-Key": apiKey, "Content-Type": "application/json" },
body: JSON.stringify({
player_id: player.accountId,
game_id: gameId,
game_mode: "standard", // must match the matchmaker's Ticket Input
preferred_az: "us-east",
rating_bucket: 0,
tags: [],
}),
});
const { ticketId } = await res.json();
| Field | Description |
|---|---|
player_id | The player being matched. |
game_id | Your GameFlow game id. |
game_mode | Routes the ticket to the matchmaker. Must equal its Ticket Input game mode. |
preferred_az | The player's region. |
mu, sigma | Optional, and omitted above. A FIFO matchmaker never reads them, and a matchmaker whose Skill Rule is paired with a Skill Rating Model node resolves the player's real rating server-side and ignores them. They are only read by a Skill Rule published without a model node, which also rejects a sigma of 0 or less. |
rating_bucket | Numeric rating bucket. Send 0 for FIFO. |
tags | Optional string tags for use in Bucket or Split nodes. |
A failed create is usually terminal: a bad request, no published matchmaker for that
mode, or a bad key leave nothing to retry, so report it and stop. The one exception is
a 503 on a matchmaker with a Skill Rule, which means the server-side rating lookup
was unavailable. That one is worth retrying.
2. Poll for the assigned server
The status endpoint long-polls: it waits up to timeout_seconds and returns as soon as
the ticket is assigned a server, or pending if still searching. Call it in a loop until
the ticket is assigned or the player cancels.
const url =
`${GAMEFLOW_API_URL}/matchmaking/tickets/${ticketId}/status?timeout_seconds=20`;
while (searching) {
const res = await fetch(url, { headers: { "X-Api-Key": apiKey } });
const { status, connection } = await res.json();
if (status === "assigned" && connection) {
const [address, port] = splitHostPort(connection); // "host:port"
sendServerToPlayer(address, port);
break;
}
// status is "pending": the request already long-polled, so just loop again.
}
For a robust loop, treat network errors, 408, 429, and 5xx as transient (back off
and retry, giving up after a few consecutive failures), and other 4xx as terminal.
3. Cancel on stop or disconnect
If the player stops searching or disconnects before matching, drop the ticket. Otherwise it lingers in the queue and gets paired with the next player, sending them to an empty server.
await fetch(
`${GAMEFLOW_API_URL}/matchmaking/tickets/${ticketId}?player_id=${playerId}`,
{ method: "DELETE", headers: { "X-Api-Key": apiKey } },
);
player_id must be the ticket owner. A 404 means the ticket was already matched or
removed, so treat it as success. Cancel from both your "stop search" handler and your
socket-close handler.
Receiving the match on the server
The status returns connection as host:port. Your backend forwards it to the player,
who connects directly to that dedicated server.
GameFlow also writes the match context into the server's payload: the match id, the game mode, and the roster with each player's ticket, team and slot. The difference from direct allocation is who produces it. There your backend already knows the teams and passes them itself; here GameFlow formed the match, so GameFlow writes it.
You can ignore the payload and seat players in arrival order as they connect, which is
enough for FIFO. Read it when you want the roster GameFlow decided, and read match_id
from it if you want to report how the match went. See
Match Analytics.
Full example
The Colyseus multiplayer example is a complete, working implementation of this FIFO flow: a backend-driven Quick Match that creates a ticket, long-polls with backoff, cancels on stop and disconnect, and a Colyseus server that seats matched players into a 2v2.
Related
- Creating a Matchmaker: build and publish the FIFO matchmaker.
- Custom Game Backend: direct allocation, when you already know the teams.
- Environment Variables & Metadata: the payload the server reads.
- Match Analytics: reporting how the match started, ended and turned out.
- Game Server SDK: the server-side lifecycle.