Skip to content
UCIK Docs
Esc
↑↓navigate↵open⌘Jpreview
On this page

EOS multiplayer and voice

Use EOS lobbies, sessions, P2P, and RTC with their distinct lifecycles.

Sign in to EOS before creating or joining. Use Crossplay lobbies and sessions when the same gameplay must work across providers.

Service Use it for Lifetime
Lobby Parties, member attributes, owner changes, lobby voice Members join and leave; the owner can destroy it
Session Advertising a game, player slots, match state Create, start, end, then destroy

EOS lobbies and sessions are separate services. Searching one does not find entries created in the other. Neither creates an Unreal listen server by itself.

Create a lobby with voice

Create a co-op lobby with voice
Create a co-op lobby with voice
Preparing your graph…

Loading the interactive viewer.

Find co-op lobbies
Find co-op lobbies
Preparing your graph…

Loading the interactive viewer.

Join a selected lobby without travel
Join a selected lobby without travel
Preparing your graph…

Loading the interactive viewer.

Leave a lobby
Leave a lobby
Preparing your graph…

Loading the interactive viewer.

Destroy an owned lobby
Destroy an owned lobby
Preparing your graph…

Loading the interactive viewer.

Game module dependencies: EIKCore, BetideCore, Engine.

#include "Engine/GameInstance.h"
#include "Interfaces/IEIKOnlineServices.h"
#include "Interfaces/IEIKLobbies.h"

bool CreateEosLobby(UGameInstance &GameInstance,
                    const FEIKAsyncCallback<FEIKCreateLobbyResult> &Completion)
{
    const auto Services = FEIKOnlineServicesFactory::Get(GameInstance.GetWorld());
    if (!Services)
        return false;
    const auto Lobbies = Services->GetLobbiesInterface();
    if (!Lobbies)
        return false;
    FEIKLobbySettings Settings;
    Settings.MaxMembers = 4;
    Settings.JoinPolicy = EEIKJoinPolicy::PublicAdvertised;
    Settings.bEnableVoiceChat = true;
    Settings.Attributes.Add(TEXT("Mode"), FEIKAttribute::String(TEXT("Coop")));
    Lobbies->CreateLobby(0, TEXT("GameLobby"), Settings, {}, Completion);
    return true;
}

void FindEosCoopLobbies(IEIKLobbies &Lobbies,
                        const FEIKAsyncCallback<FEIKFindLobbiesResult> &Completion)
{
    FEIKLobbySearchParams Search;
    Search.MaxResults = 20;
    Search.Attributes.Add(TEXT("Mode"), FEIKAttribute::String(TEXT("Coop")));
    Lobbies.FindLobbies(0, Search, Completion);
}

bool JoinSelectedEosLobby(IEIKLobbies &Lobbies, const FEIKLobby &Selected,
                          const FEIKAsyncCallback<FEIKJoinLobbyResult> &Completion)
{
    if (!Selected.IsValid())
        return false;
    Lobbies.JoinLobby(0, TEXT("GameLobby"), Selected, {}, Completion);
    return true;
}

bool LeaveEosLobby(IEIKLobbies &Lobbies, const FEIKAsyncCallbackVoid &Completion)
{
    const auto Lobby = Lobbies.GetLobbyByName(TEXT("GameLobby"));
    if (!Lobby.IsValid())
        return false;
    Lobbies.LeaveLobby(0, Lobby.LobbyId, Completion);
    return true;
}

bool DestroyOwnedEosLobby(IEIKLobbies &Lobbies, const FEIKAsyncCallbackVoid &Completion)
{
    const auto Lobby = Lobbies.GetLobbyByName(TEXT("GameLobby"));
    if (!Lobby.IsValid())
        return false;
    Lobbies.DestroyLobby(0, Lobby.LobbyId, Completion);
    return true;
}

Resolve IEIKLobbies from the current world’s services as shown above. Bind completion callbacks with CreateWeakLambda. A true return means dispatched; check the asynchronous result. Search returns Lobbies; joining through this interface does not travel.

Enable Lobby Voice Chat when creating the lobby. Lobby membership manages the RTC room; do not join that room again using a token-based voice flow.

Create EosLobbyResults (EIK Lobby array) for the search example and pass a selected entry to the join example. Search attributes match by equality in Find EIK Lobbies. Keep attribute names and types consistent between creation and search. Pass the selected result to Join EIK Lobby. Turn Auto Travel off for a party or pre-match lobby; joining successfully does not prove that a game server is ready.

For a direct EOS pre-match flow, publish Mark EIK Lobby Game Ready only after the server is reachable. Members observe On Lobby Attributes Changed, check readiness, and use Travel To EIK Lobby Game. Clear readiness when that game ends. In C++, the publishing calls are on UEIK_UpdateLobby_AsyncFunction in Lobbies/EIK_UpdateLobby_AsyncFunction.h.

Use Update EIK Lobby for lobby data and Update EIK Lobby Member Attributes for a member’s data. Bind lobby events before joining so your UI receives membership and attribute changes. Leave EIK Lobby leaves membership; Destroy EIK Lobby is an owner operation. Owner election alone does not migrate the Unreal world—see host recovery.

Find co-op sessions
Find co-op sessions
Preparing your graph…

Loading the interactive viewer.

Join a selected session
Join a selected session
Preparing your graph…

Loading the interactive viewer.

Leave a session
Leave a session
Preparing your graph…

Loading the interactive viewer.

Using the same dependencies:

#include "Engine/GameInstance.h"
#include "Interfaces/IEIKOnlineServices.h"
#include "Interfaces/IEIKSessions.h"
#include "GameFramework/PlayerController.h"

bool CreateEosSession(UGameInstance &GameInstance,
                      const FEIKAsyncCallback<FEIKCreateSessionResult> &Completion)
{
    const auto Services = FEIKOnlineServicesFactory::Get(GameInstance.GetWorld());
    if (!Services)
        return false;
    const auto Sessions = Services->GetSessionsInterface();
    if (!Sessions)
        return false;
    FEIKSessionSettings Settings;
    Settings.MaxPlayers = 4;
    Settings.JoinPolicy = EEIKJoinPolicy::PublicAdvertised;
    Settings.Attributes.Add(TEXT("Mode"), FEIKAttribute::String(TEXT("Coop")));
    Sessions->CreateSession(0, TEXT("GameSession"), Settings, Completion);
    return true;
}

void FindEosCoopSessions(IEIKSessions &Sessions,
                         const FEIKAsyncCallback<FEIKFindSessionsResult> &Completion)
{
    FEIKSessionSearchParams Search;
    Search.MaxResults = 20;
    Search.Attributes.Add(TEXT("Mode"), FEIKAttribute::String(TEXT("Coop")));
    Sessions.FindSessions(0, Search, Completion);
}

void JoinSelectedEosSession(UGameInstance &GameInstance, IEIKSessions &Sessions,
                            const FEIKSessionSearchResult &Selected,
                            const FEIKAsyncCallback<FEIKJoinSessionResult> &Completion)
{
    const TWeakObjectPtr<UGameInstance> WeakInstance(&GameInstance);
    Sessions.JoinSession(
        0, TEXT("GameSession"), Selected,
        FEIKAsyncCallback<FEIKJoinSessionResult>::CreateWeakLambda(
            &GameInstance,
            [WeakInstance, Completion](const TEIKAsyncResult<FEIKJoinSessionResult> &Result) {
                if (!WeakInstance.IsValid())
                    return;
                if (Result.bWasSuccessful && Result.Result.IsSet() &&
                    !Result.Result->ConnectString.IsEmpty())
                {
                    if (APlayerController *Player = WeakInstance->GetFirstLocalPlayerController())
                        Player->ClientTravel(Result.Result->ConnectString, TRAVEL_Absolute);
                }
                Completion.ExecuteIfBound(Result);
            }));
}

void LeaveEosSession(IEIKSessions &Sessions, const FEIKAsyncCallbackVoid &Completion)
{
    Sessions.LeaveSession(0, TEXT("GameSession"), Completion);
}

Resolve IEIKSessions from the current world’s services as above. Bind callbacks with CreateWeakLambda and check their results. StartSession, EndSession, and DestroySession also take completion callbacks.

Create EosSessionResults (EIK Session Search Result array) and pass a selected entry to the join example. Join requests travel only when a connect string and local player controller exist; success does not confirm arrival. Leaving removes local membership and, for the host, ends the advertised session. It does not return players to a menu.

Keep GameSession consistent across the lifecycle. Set Dedicated Server only on an actual dedicated server.

Start and end a match

Run these on the session owner after creating GameSession. Start marks it in progress; end keeps the session for later use. Neither operation travels players or destroys the session.

EOS session match state
Preparing your graph…

Loading the interactive viewer.

Variable: SessionChangePending — Boolean, default false.

Module dependencies: EIKCore, BetideCore, Engine.

// EosMatchGameInstance.h
#pragma once
#include "Engine/GameInstance.h"
#include "EosMatchGameInstance.generated.h"

UCLASS()
class UEosMatchGameInstance : public UGameInstance
{
    GENERATED_BODY()
  public:
    UFUNCTION(BlueprintCallable)
    void StartMatch();
    UFUNCTION(BlueprintCallable)
    void EndMatch();
    UPROPERTY(BlueprintReadOnly)
    bool SessionChangePending = false;

  private:
    void ChangeMatchState(bool bStart);
};
// EosMatchGameInstance.cpp
#include "EosMatchGameInstance.h"
#include "Interfaces/IEIKOnlineServices.h"
#include "Interfaces/IEIKSessions.h"
#include "Logging/LogMacros.h"

DEFINE_LOG_CATEGORY_STATIC(LogEosMatch, Log, All);

void UEosMatchGameInstance::StartMatch()
{
    ChangeMatchState(true);
}
void UEosMatchGameInstance::EndMatch()
{
    ChangeMatchState(false);
}

void UEosMatchGameInstance::ChangeMatchState(bool bStart)
{
    if (SessionChangePending)
    {
        UE_LOG(LogEosMatch, Warning, TEXT("A session change is already running"));
        return;
    }
    const auto Services = FEIKOnlineServicesFactory::Get(GetWorld());
    const auto Sessions = Services ? Services->GetSessionsInterface() : nullptr;
    if (!Sessions)
    {
        UE_LOG(LogEosMatch, Warning, TEXT("EOS sessions are unavailable"));
        return;
    }
    SessionChangePending = true;
    const auto Completion = FEIKAsyncCallbackVoid::CreateWeakLambda(
        this, [this, bStart](const TEIKAsyncResultVoid &Result) {
            SessionChangePending = false;
            if (!Result.IsSuccess())
            {
                UE_LOG(LogEosMatch, Warning, TEXT("%s"), *Result.Error.Message);
                return;
            }
            UE_LOG(LogEosMatch, Log, TEXT("Session %s"), bStart ? TEXT("started") : TEXT("ended"));
        });
    if (bStart)
        Sessions->StartSession(TEXT("GameSession"), Completion);
    else
        Sessions->EndSession(TEXT("GameSession"), Completion);
}

Select the EOS transport through the Crossplay preset, then use the listen-server or dedicated-server flow. A discoverable session can still have an unavailable host or incorrect net driver.

Positional voice

Add EIK Voice Chat Synth Component to the pawn class and give it an attenuation asset with spatialization on. The component plays its pawn’s player (matched by Product User ID from the player state) from the pawn instead of through EOS. Fill Supported Rooms with the RTC room names it should take, or turn on Use Global Room to take every joined room. Other players keep ordinary voice. With the crossplay kit, use Crossplay Positional Voice instead; it picks the rooms itself.

Control lobby voice

List joined voice rooms
List joined voice rooms
Preparing your graph…

Loading the interactive viewer.

List microphones
List microphones
Preparing your graph…

Loading the interactive viewer.

Select a microphone
Select a microphone
Preparing your graph…

Loading the interactive viewer.

Mute or unmute your microphone
Mute or unmute your microphone
Preparing your graph…

Loading the interactive viewer.

Add EIKBlueprints to the game module dependencies.

#include "VoiceChat/EIKVoiceChatLibrary.h"

bool SetEosMicrophoneMuted(UObject *WorldContext, bool bMuted, bool &OutMuted)
{
    OutMuted = false;
    if (!UEIKVoiceChatLibrary::SetEIKLobbyVoiceChatInputMuted(WorldContext, 0, bMuted))
        return false;
    OutMuted = UEIKVoiceChatLibrary::IsEIKLobbyVoiceChatInputMuted(WorldContext, 0);
    return true;
}

bool GetEosMicrophones(UObject *WorldContext, TArray<FEIKVoiceChatDeviceInfo> &OutDevices)
{
    OutDevices.Reset();
    if (!UEIKVoiceChatLibrary::IsVoiceChatAvailable(WorldContext, 0))
        return false;
    OutDevices = UEIKVoiceChatLibrary::GetEIKLobbyVoiceChatAvailableInputDevices(WorldContext, 0);
    return true;
}

bool SelectEosMicrophone(UObject *WorldContext, const FEIKVoiceChatDeviceInfo &Device)
{
    return UEIKVoiceChatLibrary::SetEIKLobbyVoiceChatInputDevice(WorldContext, 0, Device.DeviceId);
}

bool GetJoinedEosVoiceRooms(UObject *WorldContext, TArray<FString> &OutRooms)
{
    OutRooms.Reset();
    if (!UEIKVoiceChatLibrary::IsVoiceChatAvailable(WorldContext, 0))
        return false;
    OutRooms = UEIKVoiceChatLibrary::GetJoinedEIKLobbyVoiceChatChannels(WorldContext, 0);
    return true;
}

Pass an entry from the microphone list to the selection example. Selection reports that the voice user received the request, not that the hardware switch succeeded. Device lists can be empty. The room list includes every joined channel for that user, including token rooms. Leaving the lobby manages its room automatically.

Use the current world’s local user for voice, especially in PIE. When using EOS voice objects directly, resolve the user for that player’s PUID; a global single-user voice object may not be the logged-in player. Check microphone permission and selected input/output devices when membership works but audio does not.

Token-based voice rooms

Use a token room for voice independent of lobby membership. Sign in to EOS first; EIK creates and logs in that player’s voice user in both backends. Pass a room name and server-issued credentials for the same EOS Product User ID.

RoomCredentials is JSON containing client_base_url and participant_token, not a bare token. On your server, call Create Voice Room Token for the room and its players, then Get EIK Voice Room Join Credentials with the response and each player’s Product User ID, and send each player their own credentials. Keep the server’s privileged credentials on the server. Leave only the room when finished. The voice user is shared with lobby voice, so do not log it out or disconnect shared voice to leave one room.

With the crossplay kit, the crossplay voice nodes (mute, volume, devices, talking state, transmit mode, Get Joined Crossplay Voice Channels, Crossplay Positional Voice) act on a joined token room while no crossplay lobby voice is active. An active lobby voice keeps the controls. There is no crossplay node that joins a token room.

Join a private voice room
Join a private voice room
Preparing your graph…

Loading the interactive viewer.

Leave a private voice room
Leave a private voice room
Preparing your graph…

Loading the interactive viewer.

#include "VoiceChat/EIKVoiceChatLibrary.h"

void JoinEosVoiceRoom(UObject *WorldContext, const FString &Room, const FString &RoomCredentials,
                      const FEIKVoiceChatChannelResultDelegate &Completion)
{
    if (!UEIKVoiceChatLibrary::IsVoiceChatLoggedIn(WorldContext, 0))
    {
        Completion.ExecuteIfBound(false, Room, TEXT("Sign in to EOS before joining voice"));
        return;
    }
    UEIKVoiceChatLibrary::JoinEIKTokenVoiceChatChannel(WorldContext, 0, Room, RoomCredentials,
                                                       EEIKVoiceChatChannelType::NonPositional,
                                                       Completion);
}

void LeaveEosVoiceRoom(UObject *WorldContext, const FString &Room,
                       const FEIKVoiceChatChannelResultDelegate &Completion)
{
    UEIKVoiceChatLibrary::LeaveEIKTokenVoiceChatChannel(WorldContext, 0, Room, Completion);
}

Bind Completion to a UFUNCTION taking (bool bWasSuccessful, FString ChannelName, FString Error). Handle success and failure there; the function returning does not mean the room is joined or left.

Invites, updates, and limits

Use Send EIK Lobby Invite / Accept EIK Lobby Invite for lobbies and Send EIK Session Invite / Accept EIK Session Invite for sessions. Subscribe through UEIK_LobbyEventsSubsystem or UEIK_SessionEventsSubsystem before sending or accepting requests. Their headers are in Lobbies/ and Sessions/ in EIKBlueprints.

Client policy, join policy, capacity, and service limits still apply. Keep searchable metadata small; do not put save data or secrets in public attributes. Check the operation’s error instead of retrying a denied or full lobby indefinitely.

Was this page helpful?