---
title: "Find a match"
description: "Queue players or a party and connect them to the assigned match."
seo:
  noindex: true
---

{/* Copyright (c) 2026 Betide Studio. All Rights Reserved. */}

Choose a **Matchmaking** provider in **Betide → Crossplay** and finish its setup: [GameLift](/integrations/aws/gamelift), [Edgegap](/integrations/edgegap/matchmaking), or local lobby matchmaking. Players must already be signed in.

## Find a match

**Blueprint**

<Accordion>
  <AccordionItem title="Queue a party">
<BlueprintViewer title="Queue a party" src="/blueprints/crossplay-queue-party.txt" />
  </AccordionItem>
  <AccordionItem title="Find a match and travel">
<BlueprintViewer title="Find a match and travel" src="/blueprints/crossplay-find-match-travel.txt" />
  </AccordionItem>
</Accordion>

**C++**

Game module dependencies: `MatchmakingIntegrationKit`, `BetideCore`, `UCIKCore`.

```cpp
#include "Core/MIK_Subsystem.h"
#include "Engine/GameInstance.h"
#include "UCIKCore.h"

struct FMatchmakingBindings
{
    FDelegateHandle Found;
    FDelegateHandle State;
    FDelegateHandle Failed;
    FDelegateHandle Cancelled;
    FDelegateHandle TimedOut;
    FDelegateHandle Acceptance;
};

void StopWatchingMatchmaking(UGameInstance &GameInstance, FMatchmakingBindings &Bindings)
{
    if (auto *Service = GameInstance.GetSubsystem<UMIK_Subsystem>())
    {
        Service->OnMatchFoundNative.Remove(Bindings.Found);
        Service->OnMatchmakingStateChangedNative.Remove(Bindings.State);
        Service->OnMatchmakingFailedNative.Remove(Bindings.Failed);
        Service->OnMatchmakingCancelledNative.Remove(Bindings.Cancelled);
        Service->OnMatchmakingTimedOutNative.Remove(Bindings.TimedOut);
        Service->OnMatchAcceptancePendingNative.Remove(Bindings.Acceptance);
    }
    Bindings = {};
}

FGuid QueueForMatch(UGameInstance &GameInstance, const TArray<FMIK_MatchPlayer> &Players,
                    FMatchmakingBindings &Bindings)
{
    auto *Service = GameInstance.GetSubsystem<UMIK_Subsystem>();
    if (!Service)
        return {};
    StopWatchingMatchmaking(GameInstance, Bindings);
    Bindings.Found = Service->OnMatchFoundNative.AddWeakLambda(
        &GameInstance, [](const FMIK_MatchResult &Match, const FBetideError &Error) {
            if (Error.IsError())
            {
                UE_LOG(LogUCIK, Warning, TEXT("%s"), *Error.GetLogString());
                return;
            }
            UE_LOG(LogUCIK, Log, TEXT("Queue %s found match %s"), *Match.QueueHandle.ToString(),
                   *Match.MatchId);
        });
    Bindings.State = Service->OnMatchmakingStateChangedNative.AddWeakLambda(
        &GameInstance,
        [](FGuid Queue, const FMIK_MatchmakingStatus &Status, const FBetideError &Error) {
            UE_LOG(LogUCIK, Log, TEXT("%s: %s"), *Queue.ToString(), *Status.StatusMessage);
            if (Error.IsError())
                UE_LOG(LogUCIK, Warning, TEXT("%s"), *Error.GetLogString());
        });
    Bindings.Failed = Service->OnMatchmakingFailedNative.AddWeakLambda(
        &GameInstance, [](FGuid Queue, EMIK_MatchmakingState, const FBetideError &Error) {
            UE_LOG(LogUCIK, Warning, TEXT("Queue %s failed: %s"), *Queue.ToString(),
                   *Error.GetLogString());
        });
    Bindings.Cancelled = Service->OnMatchmakingCancelledNative.AddWeakLambda(
        &GameInstance, [](FGuid Queue, const FBetideError &Error) {
            UE_LOG(LogUCIK, Log, TEXT("Queue %s cancelled: %s"), *Queue.ToString(),
                   *Error.GetLogString());
        });
    Bindings.TimedOut = Service->OnMatchmakingTimedOutNative.AddWeakLambda(
        &GameInstance, [](FGuid Queue, const FBetideError &Error) {
            UE_LOG(LogUCIK, Warning, TEXT("Queue %s timed out: %s"), *Queue.ToString(),
                   *Error.GetLogString());
        });
    Bindings.Acceptance = Service->OnMatchAcceptancePendingNative.AddWeakLambda(
        &GameInstance, [](FGuid Queue, const FMIK_MatchmakingStatus &) {
            UE_LOG(LogUCIK, Log, TEXT("Queue %s is waiting for the player's answer"),
                   *Queue.ToString());
        });
    FMIK_QueueConfig Config;
    Config.QueueName = TEXT("QuickPlay");
    Config.MinPlayers = 2;
    Config.MaxPlayers = 4;
    Config.NumTeams = 0;
    Config.Players = Players;
    const FGuid Queue = Service->StartMatchmaking(Config);
    if (!Queue.IsValid())
        UE_LOG(LogUCIK, Warning, TEXT("Could not start matchmaking"));
    return Queue;
}
```

For the same lobby-to-player conversion as Blueprint, add `UCIKNodes` to your dependencies:

```cpp
#include "Matchmaking/UCIK_MatchmakingLibrary.h"

FGuid QueueForParty(UGameInstance &GameInstance, const FUCIK_Lobby &PartyLobby,
                    FMatchmakingBindings &Bindings)
{
    return QueueForMatch(GameInstance,
                         UUCIK_MatchmakingLibrary::MakeMatchPlayersFromLobby(PartyLobby), Bindings);
}
```

Store the queue handle and C++ bindings on the owning GameInstance. Watch queue events before starting and stop watching when leaving the matchmaking screen. Events include the queue handle so your UI can route updates.

Use **Start Matchmaking** when your game handles the result. **Find Match And Travel** also handles travel; do not run both for the same request. For a party, only the leader queues the participating members. Pass a current lobby with its member list populated.

**External server travel is currently blocked:** the shared matchmaking path stores a complete `host:port` URL in **Connect Info → Host** and retains the port. Both travel paths append it again. This affects Edgegap and FlexMatch assignments; the compiled examples below do not prove that these connections work.

Current source exposes **Error** on **Find Match And Travel → On Failure**. The existing export predates that pin and needs refreshing after the matchmaking fixes. Its wrapper also filters callbacks by a queue handle assigned only after starting; a synchronous startup failure can be missed.

## Accept, cancel, and connect

**Blueprint**

<Accordion>
  <AccordionItem title="Watch queue status and acceptance">
<BlueprintViewer title="Watch queue status and acceptance" src="/blueprints/crossplay-watch-matchmaking.txt" />
  </AccordionItem>
  <AccordionItem title="Answer a proposed match">
<BlueprintViewer title="Answer a proposed match" src="/blueprints/crossplay-accept-match.txt" />
  </AccordionItem>
  <AccordionItem title="Cancel a queued match">
<BlueprintViewer title="Cancel a queued match" src="/blueprints/crossplay-cancel-matchmaking.txt" />
  </AccordionItem>
  <AccordionItem title="Travel to the matched server">
<BlueprintViewer title="Travel to the matched server" src="/blueprints/crossplay-travel-connect-info.txt" />
  </AccordionItem>
</Accordion>

**C++**

```cpp
#include "Core/MIK_Subsystem.h"
#include "Engine/GameInstance.h"
#include "Travel/UCIKTravelSubsystem.h"
#include "UCIKCore.h"

void AnswerProposedMatch(UGameInstance &GameInstance, FGuid QueueHandle, bool bPlayerAccepted)
{
    if (auto *Service = GameInstance.GetSubsystem<UMIK_Subsystem>())
    {
        Service->AcceptMatch(QueueHandle, bPlayerAccepted,
                             FBetideCallbackVoid::CreateWeakLambda(
                                 &GameInstance, [](const TBetideResult<void> &Result) {
                                     if (!Result.bOk)
                                         UE_LOG(LogUCIK, Warning, TEXT("%s"),
                                                *Result.Error.GetLogString());
                                     else
                                         UE_LOG(LogUCIK, Log, TEXT("Match response submitted"));
                                 }));
    }
}

void CancelQueuedMatch(UGameInstance &GameInstance, FGuid QueueHandle)
{
    if (auto *Service = GameInstance.GetSubsystem<UMIK_Subsystem>())
        Service->CancelMatchmaking(QueueHandle);
}

void TravelToMatchedServer(UGameInstance &GameInstance, const FUCIK_ConnectInfo &ConnectInfo)
{
    if (auto *Service = GameInstance.GetSubsystem<UUCIKTravelSubsystem>())
    {
        Service->TravelToConnectInfo(ConnectInfo,
                                     FBetideCallback<bool>::CreateWeakLambda(
                                         &GameInstance, [](const TBetideResult<bool> &Result) {
                                             if (!Result.bOk)
                                                 UE_LOG(LogUCIK, Warning, TEXT("%s"),
                                                        *Result.Error.GetLogString());
                                             else
                                                 UE_LOG(LogUCIK, Log, TEXT("Travel requested"));
                                         }));
    }
}
```

Answer only after **On Match Acceptance Pending** and the player's choice. The travel example requires valid **Connect Info**; do not pass the affected external matchmaking result until the shared fix lands. Reservation tokens need your game's admission flow; travel does not transmit them.

For external queues, cancellation and timeout stop local processing without reporting remote ticket-deletion errors. Do not show them as confirmed backend cleanup.

<Accordion>
  <AccordionItem title="Queue configuration and provider limits">

An empty **Matchmaking Provider** uses project policy. Naming an unavailable provider fails the queue instead of silently choosing another. Match queue names and typed attributes to the provider's profile or rule set. Supply measured latency in milliseconds when placement uses regions.

Acceptance, backfill, skill updates, and server allocation depend on the selected provider. Local queue tuning does not replace remote service rules. `GetProviderOverview()` reports configured provider availability; `GetQueueStatusIncludingCompleted()` keeps terminal results available to UI.

  </AccordionItem>
</Accordion>
