Skip to main content

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();
FieldDescription
player_idThe player being matched.
game_idYour GameFlow game id.
game_modeRoutes the ticket to the matchmaker. Must equal its Ticket Input game mode.
preferred_azThe player's region.
mu, sigmaOptional, 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_bucketNumeric rating bucket. Send 0 for FIFO.
tagsOptional 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.