---
title: "Host a listen server"
description: "Start gameplay from a lobby and connect players when the host is ready."
seo:
  noindex: true
---

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

Keep these flows in your **Game Instance** Blueprint so they survive map travel. Configure **Transport** in **Betide → Crossplay** and package the gameplay map. The host must [create a lobby](/crossplay/lobbies); the joining flow below takes a lobby search result.

## Host the lobby match

**Blueprint**

<BlueprintViewer title="Host the lobby match" src="/blueprints/crossplay-host-lobby.txt" />

**C++**

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

```cpp
#include "Engine/GameInstance.h"
#include "Lobbies/UCIKLobbySubsystem.h"
#include "UCIKCore.h"

void HostPartyMatch(UGameInstance &GameInstance)
{
    auto *Service = GameInstance.GetSubsystem<UUCIKLobbySubsystem>();
    if (!Service)
        return;

    TMap<FString, FString> Options;
    Service->HostLobbyListenServer(TEXT("GameLobby"), TEXT("/Game/Maps/Arena"), Options,
                                   FBetideCallback<FUCIK_Lobby>::CreateWeakLambda(
                                       &GameInstance, [](const TBetideResult<FUCIK_Lobby> &Result) {
                                           if (!Result.bOk)
                                           {
                                               UE_LOG(LogUCIK, Warning, TEXT("%s"),
                                                      *Result.Error.GetLogString());
                                               return;
                                           }
                                           UE_LOG(LogUCIK, Log, TEXT("Lobby match ready: %s"),
                                                  *Result.GetValue().Name.ToString());
                                       }));
}
```

Replace the map path with your packaged map. Only the lobby host should call this. The plugin opens the listen world, waits for its provider address, then advertises that the game is ready. A fixed Delay is not a readiness check.

## Travel when the host is ready

**Blueprint**

<BlueprintViewer title="Travel when the host is ready" src="/blueprints/crossplay-travel-ready-lobby.txt" />

**C++**

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

```cpp
#include "Engine/GameInstance.h"
#include "Lobbies/UCIKLobbySubsystem.h"
#include "Travel/UCIKTravelSubsystem.h"
#include "UCIKCore.h"
#include "UCIKLobbyEventsSubsystem.h"

FDelegateHandle JoinAndWaitForMatch(UGameInstance &GameInstance, const FUCIK_Lobby &SelectedLobby)
{
    auto *Events = GameInstance.GetSubsystem<UUCIK_LobbyEventsSubsystem>();
    auto *Lobbies = GameInstance.GetSubsystem<UUCIKLobbySubsystem>();
    auto *Travel = GameInstance.GetSubsystem<UUCIKTravelSubsystem>();
    if (!Events || !Lobbies || !Travel)
        return {};

    const TWeakObjectPtr<UGameInstance> WeakGameInstance(&GameInstance);
    const auto Binding = MakeShared<FDelegateHandle>();
    const FUCIK_LobbyHandle ExpectedLobby = SelectedLobby.Primary;
    *Binding = Events->OnLobbyGameConnectionAvailableNative.AddWeakLambda(
        &GameInstance, [WeakGameInstance, Binding, ExpectedLobby](const FUCIK_Lobby &ReadyLobby) {
            if (!(ReadyLobby.Primary == ExpectedLobby) &&
                !ReadyLobby.Mirrors.Contains(ExpectedLobby))
                return;
            auto *Game = WeakGameInstance.Get();
            if (!Game)
                return;
            Game->GetSubsystem<UUCIK_LobbyEventsSubsystem>()
                ->OnLobbyGameConnectionAvailableNative.Remove(*Binding);
            Game->GetSubsystem<UUCIKTravelSubsystem>()->TravelToLobby(
                ReadyLobby,
                FBetideCallback<bool>::CreateWeakLambda(Game, [](const TBetideResult<bool> &Result) {
                    if (!Result.bOk)
                        UE_LOG(LogUCIK, Warning, TEXT("%s"), *Result.Error.GetLogString());
                    else
                        UE_LOG(LogUCIK, Log, TEXT("Travel requested"));
                }));
        });

    Lobbies->JoinLobby(
        SelectedLobby, false,
        FBetideCallback<FUCIK_Lobby>::CreateWeakLambda(
            &GameInstance, [WeakGameInstance, Binding](const TBetideResult<FUCIK_Lobby> &Result) {
                if (Result.bOk)
                {
                    UE_LOG(LogUCIK, Log, TEXT("Joined lobby; waiting for host"));
                    return;
                }
                if (auto *Game = WeakGameInstance.Get())
                    Game->GetSubsystem<UUCIK_LobbyEventsSubsystem>()
                        ->OnLobbyGameConnectionAvailableNative.Remove(*Binding);
                UE_LOG(LogUCIK, Warning, TEXT("%s"), *Result.Error.GetLogString());
            }));
    return *Binding;
}

void StopWaitingForMatch(UGameInstance &GameInstance, FDelegateHandle Binding)
{
    if (auto *Events = GameInstance.GetSubsystem<UUCIK_LobbyEventsSubsystem>())
        Events->OnLobbyGameConnectionAvailableNative.Remove(Binding);
}
```

Pass a search result to **JoinAndWaitForMatch**. The Blueprint stores it in `PendingLobby` (Crossplay Lobby) and ignores readiness events from other lobbies. Keep one join attempt active at a time. Call **StopWaitingForMatch** if the player leaves before the match starts. Turn off **Party Follows Host** when using this manual travel flow.

## Host without a lobby

**Blueprint**

<BlueprintViewer title="Host without a lobby" src="/blueprints/crossplay-host-listen.txt" />

**C++**

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

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

void StartListenMatch(UGameInstance &GameInstance)
{
    auto *Travel = GameInstance.GetSubsystem<UUCIKTravelSubsystem>();
    if (!Travel)
        return;

    const FBetideError Error = Travel->SetGameNetDriverTransport({});
    if (Error.IsError())
    {
        UE_LOG(LogUCIK, Warning, TEXT("%s"), *Error.GetLogString());
        return;
    }
    Travel->HostListenServer(TEXT("/Game/Maps/Arena"), {},
                             FBetideCallback<FUCIK_ConnectInfo>::CreateWeakLambda(
                                 &GameInstance, [](const TBetideResult<FUCIK_ConnectInfo> &Result) {
                                     if (!Result.bOk)
                                         UE_LOG(LogUCIK, Warning, TEXT("%s"),
                                                *Result.Error.GetLogString());
                                     else
                                         UE_LOG(LogUCIK, Log, TEXT("Listen server ready"));
                                 }));
}
```

An empty **Provider** restores your configured transport for the next map load. Publish **Connect Info** through your session or matchmaking flow; use [session travel](/crossplay/sessions) for joined session results.

<Accordion>
  <AccordionItem title="Connection checks">

The selected transport must match the active **GameNetDriver**. Let UCIK configure and validate it before travel. Do not assemble P2P addresses or overwrite reserved lobby metadata.

From a client or standalone world, hosting opens the map with `?listen`; from an authoritative server world, it uses server travel. A successful travel request is not proof that the remote client connected: handle Unreal network/travel failures and verify the second player reaches the match.

Direct-IP hosts need a reachable game port. Test internet joins on separate machines as well as local play.

  </AccordionItem>
</Accordion>
