Matchmaker Integration

Gameye + AWS FlexMatch

STANDALONE mode. Keep your matchmaking rules. Replace GameLift. Dedicated server sessions in under 0.5 seconds.

AWS FlexMatch STANDALONE mode Lambda bridge No egress fees

AWS FlexMatch is a capable matchmaking rules engine — skill brackets, team balance, latency thresholds, custom logic. If you've built your matchmaking rules in FlexMatch, they work. There's no reason to rebuild them.

What FlexMatch doesn't give you well is the server side: GameLift's egress charges, VM provisioning times, and single-cloud lock-in are what drive studios to look for alternatives. The Gameye + FlexMatch integration separates the two concerns cleanly: FlexMatch finds the match, Gameye starts the server.

How it works
  1. Players submit match tickets — FlexMatch runs your matchmaking rules
  2. Match found — FlexMatch publishes MatchmakingSucceeded to SNS/EventBridge
  3. Lambda bridge receives the event and selects the optimal Gameye region from player latency data
  4. Lambda calls Gameye's Session API — server starts in the closest available datacenter (~0.5s)
  5. Lambda notifies players with the server IP and port
  6. Players connect directly to the game server — Gameye is not in the network path

What changes vs. GameLift-hosted FlexMatch

FlexMatch + GameLift FlexMatch + Gameye
Matchmaking rules FlexMatch FlexMatch (unchanged)
Server allocation GameLift Session Queue Gameye Session API
Server location AWS regions only 21 providers, 200+ locations
Egress fees ~$0.09/GB None
Session start time 1–5 min (VM provisioning) ~0.5 seconds (pre-pulled containers)
Engine SDK required GameLift Server SDK None — plain Linux process
Infrastructure lock-in AWS Provider-agnostic

The key change in your codebase: one Lambda function that calls POST /session instead of creating a GameLift game session. Everything upstream — matchmaking configuration, rule set, ticket submission — is identical.


What you need

Linux server binary

Gameye uses container-based orchestration — the game server must compile to a Linux headless binary. Unreal Engine and Unity both support this target.

Docker image

Package your Linux binary as a Docker image and push to any OCI-compatible registry (Docker Hub, GitLab Registry, ECR). Gameye pre-pulls images onto infrastructure — no cold-pull delay at session start.

Gameye API token

Request sandbox access at /get-access/. Provisioned within 24 hours. Your image is registered during onboarding.

FlexMatch STANDALONE config

One field change in your matchmaking config: set FlexMatchMode to STANDALONE. Your rule set and all other settings are untouched.


Integration

1

Update your FlexMatch configuration

Set FlexMatchMode to STANDALONE and point notifications to an SNS topic. In standalone mode, gameSessionInfo.ipAddress and gameSessionInfo.port will be empty in the event payload — your Lambda fills them in.

JSON
{
  "Name": "your-matchmaking-config",
  "FlexMatchMode": "STANDALONE",
  "RequestTimeoutSeconds": 30,
  "AcceptanceRequired": false,
  "BackfillMode": "MANUAL",
  "RuleSetName": "your-ruleset",
  "NotificationTarget": "arn:aws:sns:us-east-1:YOUR_ACCOUNT:matchmaking-events"
}
2

Collect Gameye region latency in your game client

Gameye exposes pingable IPs per region via the GET /available-location endpoint. Have clients measure latency to these before submitting a ticket and pass the results as playerAttributes. Your Lambda uses them to select the optimal server location for the match.

TypeScript
// Fetch available regions for your image
const { locations } = await fetch(
  'https://api.gameye.io/available-location/your-image-name',
  { headers: { Authorization: `Bearer ${GAMEYE_TOKEN}` } }
).then(r => r.json());

// Measure latency to each region's pingable IP
const latencies: Record<string, number> = {};
for (const loc of locations) {
  latencies[loc.location] = await measurePing(loc.latencyIp); // ms
}

// Submit ticket — include latency as playerAttributes
await gamelift.startMatchmaking({
  ConfigurationName: 'your-matchmaking-config',
  Players: [{
    PlayerId: player.id,
    PlayerAttributes: {
      skill:             { N: player.mmr },
      gameye_europe_ms:  { N: latencies['europe']    ?? 9999 },
      gameye_useast_ms:  { N: latencies['us-east']   ?? 9999 },
      gameye_asia_ms:    { N: latencies['asia-east'] ?? 9999 },
    }
  }]
}).promise();
3

Deploy the Lambda bridge

Create a Lambda triggered by EventBridge on MatchmakingSucceeded events. It selects the best Gameye region from the matched players' latency attributes, calls POST /session, and notifies players with the returned host and port.

TypeScript
export async function handler(event: EventBridgeEvent) {
  const { matchId, tickets } = event.detail;
  const players = tickets.flatMap(t => t.players);

  // Select region with lowest worst-case latency across all players
  const region = selectBestRegion(players);

  // Start Gameye session
  const res = await fetch('https://api.gameye.io/session', {
    method: 'POST',
    headers: {
      Authorization: `Bearer ${process.env.GAMEYE_API_TOKEN}`,
      'Content-Type': 'application/json',
    },
    body: JSON.stringify({
      location: region,
      image:    'your-image-name',
      version:  process.env.GAME_IMAGE_VERSION,
      args:     ['/Game/Maps/Gameplay', `-maxplayers=${players.length}`],
      labels:   { match_id: matchId },
      ttl:      3600,
    }),
  });

  const session = await res.json();
  // session.host  → IP address
  // session.ports → [{ type: 'udp', container: 7777, host: 34521 }]
  const port = session.ports.find(p => p.type === 'udp').host;

  await saveMatchSession(matchId, session.host, port); // e.g. DynamoDB
  await notifyPlayers(players, { host: session.host, port });
}

function selectBestRegion(players: FlexMatchPlayer[]): string {
  const regions = {
    'europe':    'gameye_europe_ms',
    'us-east':   'gameye_useast_ms',
    'asia-east': 'gameye_asia_ms',
  };
  return Object.entries(regions)
    .map(([region, attr]) => ({
      region,
      worstMs: Math.max(...players.map(p => p.playerAttributes?.[attr]?.N ?? 9999))
    }))
    .sort((a, b) => a.worstMs - b.worstMs)[0].region;
}

Port mapping. The external port in ports[].host is what players connect to — it is not always 7777. Always use the value from the session response, not a hardcoded port.

Idempotency. EventBridge delivers events at-least-once. Guard against duplicate Lambda invocations with a conditional write before creating a session — check whether one already exists for matchId.

4

Notify players

FlexMatch standalone does not push connection details to players — your backend handles this.

Simpler

Polling

Clients poll a /match-status endpoint every 500ms. The Lambda writes session host and port to a data store; the endpoint returns it when ready.

5

Track session lifecycle

Call Gameye's player join and leave endpoints from your game server backend when players connect and disconnect. This gives you accurate player counts and supports backfill:

HTTP
# Player connected
POST https://api.gameye.io/session/player/join
{ "sessionId": "session-id", "playerId": "player-id" }

# Player disconnected
POST https://api.gameye.io/session/player/leave
{ "sessionId": "session-id", "playerId": "player-id" }

When the match ends, your server exits cleanly. Gameye detects process exit and reclaims the container. The ttl on the session is a backstop for any server that fails to exit.


Session lifecycle

FlexMatch: MatchmakingSucceededLambda triggered via EventBridge
Lambda creates Gameye session~0.5s — returns host IP and port
Players notified and connectDirect connection — no relay
POST /session/player/join × NBackend calls as players connect
Match runningGameye not in network path
POST /session/player/leave × NBackend calls as players disconnect
Match ends → server exits → container reclaimedBilling stops on process exit

Backfill

If players drop mid-match, keep BackfillMode: MANUAL. When a player leaves:

  1. Your game server notifies the backend of the open slot.
  2. Backend calls StartMatchBackfill with the current session roster.
  3. FlexMatch finds a replacement and fires MatchmakingBackfillSucceeded.
  4. Lambda receives the event — no new session — reads the existing session from your data store and notifies the new player with the current host and port.
  5. Backend calls POST /session/player/join for the new player.

Migration from GameLift

If your game is live on FlexMatch + GameLift today, this migration can be done without player-facing downtime:

  1. 1
    Register your Docker image with Gameye — onboarding, no code changes required.
  2. 2
    Deploy the Lambda bridge in parallel — it doesn't affect live GameLift sessions.
  3. 3
    Create a new FlexMatch config in STANDALONE mode pointing to the Lambda.
  4. 4
    Route a small percentage of traffic to the new config via a feature flag in your matchmaking request logic.
  5. 5
    Validate — session starts, latency, cost metrics.
  6. 6
    Shift 100% of traffic and decommission your GameLift fleet.

The FlexMatch rule set and matchmaking logic are identical between configs — there is nothing to rewrite.


Frequently asked questions

Can I use AWS FlexMatch without GameLift?

Yes. AWS FlexMatch supports STANDALONE mode, where it handles matchmaking independently of GameLift fleet management. When a match is found, FlexMatch publishes a MatchmakingSucceeded event to SNS/EventBridge. A Lambda function receives that event and calls any server allocation API — including Gameye's Session API.

Do I need to rewrite my FlexMatch rule set?

No. Your rule set, matchmaking configuration, and ticket submission logic are unchanged. The only change is setting FlexMatchMode to STANDALONE and deploying a Lambda that calls Gameye's POST /session instead of creating a GameLift game session.

How does region selection work without GameLift queues?

Gameye exposes pingable IPs per region via the GET /available-location endpoint. Game clients measure latency to these IPs before matchmaking and submit the results as FlexMatch playerAttributes. The Lambda bridge aggregates those measurements across matched players and selects the region with the lowest worst-case latency — ensuring no one player has a significantly worse experience.

Does this work with FlexMatch backfill?

Yes, with BackfillMode: MANUAL. When a player drops and you call StartMatchBackfill, FlexMatch fires MatchmakingBackfillSucceeded. The Lambda checks whether a session already exists for the matchId — if it does, it's a backfill, so it notifies the new player with the existing session's host and port rather than starting a new server.

Do I need to remove the GameLift Server SDK from my game server?

Gameye doesn't require an engine-side SDK. Your server runs as a plain Linux process inside a container — no GameLift SDK, no Gameye SDK. Removing the GameLift SDK is a one-time cleanup during migration, but it's not required immediately; the server will run without it.

Get started

Sandbox access in 24 hours.

Request your API token, push your image, and start your first session — all before your next sprint ends. No sales call required.