Matchmaker Integration
Gameye + Unity Matchmaker
Cloud Code module. Keep your queues and rules. Replace Unity GSMS. Dedicated server sessions in under 0.5 seconds.
Updated May 5, 2026
Unity Matchmaker is a capable rules-based matchmaking service — skill brackets, team composition, latency thresholds, custom attributes. If you've built your matchmaking logic in Unity Matchmaker queues, it works. There's no reason to rebuild it.
What Unity GSMS (Game Server Managed Services) doesn't give you well is coverage and cost: egress fees, limited datacenter footprint, and tight coupling to the Unity platform are what drive studios to look for alternatives. The Gameye + Unity Matchmaker integration separates the two concerns cleanly: Unity Matchmaker finds the match, Gameye starts the server — via a Cloud Code module that deploys in under 15 minutes.
- Players submit match tickets — Unity Matchmaker runs your queue rules
- Match found — Unity Matchmaker invokes the Gameye Cloud Code module
- Module reads player latency attributes and selects the optimal Gameye region
- Module calls Gameye's Session API — server starts in the nearest datacenter (~0.5s)
- Module returns
ipv4Addressandportto Unity Matchmaker - Players receive a
MatchAssignmentvia the Unity Matchmaker SDK and connect directly
What changes vs. Unity GSMS
| Unity Matchmaker + GSMS | Unity Matchmaker + Gameye | |
|---|---|---|
| Matchmaking rules | Unity Matchmaker | Unity Matchmaker (unchanged) |
| Server allocation | Unity GSMS fleet | Gameye Session API via Cloud Code |
| Server location | Unity Multiplay datacenters | 21 providers, 200+ locations |
| Egress fees | ~$0.08/GB | None |
| Session start time | 30s–5min (VM provisioning) | ~0.5 seconds (pre-pulled containers) |
| Engine SDK required | Unity GSMS Server SDK | None — plain Linux process |
| Infrastructure lock-in | Unity platform | Provider-agnostic |
The key change in your codebase: one Cloud Code module that calls POST /session instead of allocating a Unity GSMS server. Everything upstream — queue configuration, pool rules, ticket submission, player SDK — is identical.
What you need
Gameye uses container-based orchestration — the game server must compile to a Linux headless binary. Unity supports this via the Standalone Linux build target with the Dedicated Server module installed.
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.
You need an active Unity Gaming Services project with Unity Matchmaker enabled. Install the UGS CLI (npm i -g ugs) to deploy the Cloud Code module.
Integration
Store your Gameye token as a Cloud Code secret
The Cloud Code module needs your Gameye API token at runtime. Store it as a secret via the UGS CLI — never hardcode it in the module source.
# Authenticate with UGS
ugs login
# Store Gameye token as a Cloud Code secret
ugs cloud-code secrets create GAMEYE_API_TOKEN --value "your-gameye-api-token" --project-id $UNITY_PROJECT_ID --environment-name production Secrets are injected into the module's context.secrets object at invocation time and are never exposed in logs or module source.
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 match ticket and pass the results as custom playerAttributes. The Cloud Code module uses them to select the optimal server location for the match.
// Fetch available Gameye regions for your image
var locResp = await UnityWebRequest.Get(
"https://api.gameye.io/available-location/your-image-name"
).SendWebRequest();
var locations = JsonUtility.FromJson<LocationList>(locResp.downloadHandler.text);
// Measure round-trip latency to each region's pingable IP
var latencies = new Dictionary<string, float>();
foreach (var loc in locations.items) {
latencies[loc.location] = await PingAsync(loc.latencyIp); // ms
}
// Build ticket attributes — include Gameye latencies
var attributes = new Dictionary<string, object> {
["skill"] = player.mmr,
["gameye_europe_ms"] = latencies.GetValueOrDefault("europe", 9999f),
["gameye_useast_ms"] = latencies.GetValueOrDefault("us-east", 9999f),
["gameye_asia_ms"] = latencies.GetValueOrDefault("asia-east",9999f),
};
// Submit the ticket
await MatchmakerService.Instance.CreateTicketAsync(
new List<Unity.Services.Matchmaker.Models.Player> {
new(AuthenticationService.Instance.PlayerId, attributes)
},
new CreateTicketOptions { QueueName = "ranked-queue" }
); Deploy the Cloud Code module
Create the allocation module. Unity Matchmaker calls it with the full match payload — teams, players, and their attributes. The module selects the best Gameye region, starts the session, and returns the connection details.
// gameye-allocator/index.js
// Deploy with: ugs cloud-code modules deploy ./gameye-allocator/
module.exports = {
async Allocate({ params, context, logger }) {
const { matchId, teams } = params;
const players = teams.flatMap(t => t.players);
// Select region with lowest worst-case latency across all players
const region = selectBestRegion(players);
logger.info(`Starting Gameye session in ${region} for match ${matchId}`);
const res = await fetch('https://api.gameye.io/session', {
method: 'POST',
headers: {
Authorization: `Bearer ${context.secrets.GAMEYE_API_TOKEN}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
location: region,
image: 'your-image-name',
version: context.projectConfig.GAME_VERSION,
args: ['-dedicatedServer', `-maxPlayers=${players.length}`],
labels: { match_id: matchId },
ttl: 3600,
}),
});
if (!res.ok) {
throw new Error(`Gameye session API error: ${res.status}`);
}
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;
// Return format expected by Unity Matchmaker
return { ipv4Address: session.host, port: String(port) };
}
};
function selectBestRegion(players) {
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.attributes?.[attr] ?? 9999))
}))
.sort((a, b) => a.worstMs - b.worstMs)[0].region;
} # Deploy the module to your UGS project
ugs cloud-code modules deploy ./gameye-allocator/ --project-id $UNITY_PROJECT_ID --environment-name production
# Confirm deployment
ugs cloud-code modules list --project-id $UNITY_PROJECT_ID --environment-name production 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.
Configure your Unity Matchmaker queue
Update your queue configuration in the UGS Dashboard (or via CLI) to use Cloud Code allocation. Point it to the Allocate function in the module you just deployed.
{
"name": "ranked-queue",
"defaultEscalationRules": [],
"pools": [
{
"name": "default",
"matchLogic": {
"type": "Sequence",
"rules": [
{ "type": "TeamCountRule", "min": 2, "max": 2 },
{ "type": "TeamSizeRule", "min": 5, "max": 5 }
]
},
"teams": [
{ "name": "team-a", "minPlayers": 5, "maxPlayers": 5 },
{ "name": "team-b", "minPlayers": 5, "maxPlayers": 5 }
],
"serverAllocation": {
"type": "cloudCode",
"cloudCodeConfig": {
"moduleName": "gameye-allocator",
"functionName": "Allocate"
}
}
}
]
} Existing queues. You can update a live queue's serverAllocation config without disrupting in-flight tickets. Tickets already matched are not affected; new matches immediately use the Cloud Code allocator.
Track session lifecycle
Call Gameye's player join and leave endpoints from your game server or backend when players connect and disconnect. This gives you accurate player counts and enables correct billing on process exit:
# 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
Migration from Unity GSMS
If your game is live on Unity Matchmaker + GSMS today, this migration can be done without player-facing downtime:
- 1 Register your Docker image with Gameye — onboarding, no code changes required.
- 2 Remove the Unity GSMS Server SDK from your game server binary — Gameye doesn't need it. Your server becomes a plain Linux process.
- 3 Deploy the Cloud Code module via UGS CLI — does not affect your live queue configuration yet.
- 4 Clone your queue to a test queue with
serverAllocation.type: cloudCodepointing to the module. - 5 Route a small percentage of tickets to the test queue via a feature flag in your ticket submission logic.
- 6 Validate — session starts, latency, cost metrics.
- 7 Update your production queue to the Cloud Code allocator and decommission your GSMS fleet.
Your Unity Matchmaker queue rules, player SDK integration, and ticket submission code are identical between the GSMS queue and the Gameye queue — there is nothing to rewrite.
Frequently asked questions
Can I use Unity Matchmaker without Unity GSMS?
Yes. Unity Matchmaker supports custom server allocation via a Cloud Code module. When a match is found, Unity Matchmaker invokes the module, which calls Gameye's Session API. Players still receive a MatchAssignment via the Unity Matchmaker SDK — the flow is identical from the player's perspective.
Do I need to rewrite my Unity Matchmaker queues?
No. Your queue configuration, pool rules, and ticket submission code are unchanged. The only change is updating serverAllocation.type from multiplay to cloudCode and pointing it to the Gameye allocator module.
How does region selection work?
Gameye exposes pingable IPs per region via the GET /available-location endpoint. Game clients measure latency to these IPs before submitting a ticket and include them as custom playerAttributes. The Cloud Code module reads those measurements from the matched players, selects the region with the lowest worst-case latency, and passes it to POST /session.
Does my game server need the Unity GSMS Server SDK?
No. Gameye runs your server as a plain Linux process inside a container — no Unity GSMS Server SDK, no Gameye SDK. You can remove the Unity GSMS lifecycle SDK from your server project entirely. Player state is tracked via your backend calling the Gameye /session/player/join and /session/player/leave endpoints.
How long does the Cloud Code module deployment take?
The module deploys in under 15 minutes using the UGS CLI. Once deployed, you update your queue configuration via the UGS Dashboard or CLI. No redeployment of your game client or server binary is required — only the Cloud Code module and queue config change.
Does this work with Unity Matchmaker backfill?
Yes. For backfill, configure a separate Cloud Code function (or a conditional branch in Allocate) that checks whether a session already exists for the match. If it does, return the existing host and port from your data store rather than creating a new session. Unity Matchmaker delivers the MatchAssignment to the new player — no new server starts.
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.