Matchmaker Integration
Gameye + AWS FlexMatch
STANDALONE mode. Keep your matchmaking rules. Replace GameLift. Dedicated server sessions in under 0.5 seconds.
Updated March 31, 2026
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.
- Players submit match tickets — FlexMatch runs your matchmaking rules
- Match found — FlexMatch publishes
MatchmakingSucceededto SNS/EventBridge - Lambda bridge receives the event and selects the optimal Gameye region from player latency data
- Lambda calls Gameye's Session API — server starts in the closest available datacenter (~0.5s)
- Lambda notifies players with the server IP and port
- 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
Gameye uses container-based orchestration — the game server must compile to a Linux headless binary. Unreal Engine and Unity both support this target.
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.
Request sandbox access at /get-access/. Provisioned within 24 hours. Your image is registered during onboarding.
One field change in your matchmaking config: set FlexMatchMode to STANDALONE. Your rule set and all other settings are untouched.
Integration
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.
{
"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"
} 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.
// 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(); 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.
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.
Notify players
FlexMatch standalone does not push connection details to players — your backend handles this.
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.
WebSocket push
Persistent WebSocket per player via API Gateway. Lambda pushes connection details directly — players receive the server address within ~1 second of match found.
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:
# 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
Backfill
If players drop mid-match, keep BackfillMode: MANUAL. When a player leaves:
- Your game server notifies the backend of the open slot.
- Backend calls
StartMatchBackfillwith the current session roster. - FlexMatch finds a replacement and fires
MatchmakingBackfillSucceeded. - 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.
- Backend calls
POST /session/player/joinfor 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 Register your Docker image with Gameye — onboarding, no code changes required.
- 2 Deploy the Lambda bridge in parallel — it doesn't affect live GameLift sessions.
- 3 Create a new FlexMatch config in
STANDALONEmode pointing to the Lambda. - 4 Route a small percentage of traffic to the new config via a feature flag in your matchmaking request logic.
- 5 Validate — session starts, latency, cost metrics.
- 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.