---
title: "Record game events"
description: "Send useful gameplay and diagnostic events through the configured service."
seo:
  noindex: true
---

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

Configure [CloudWatch](/integrations/aws/cloudwatch) or another registered telemetry provider. Ask the player before recording: unanswered consent is treated as no, and the choice is stored on this machine.

## Record an event

**Blueprint**

<Accordion>
  <AccordionItem title="Save and check consent">
<BlueprintViewer title="Save and check consent" src="/blueprints/crossplay-telemetry-consent.txt" />
  </AccordionItem>
  <AccordionItem title="Record a match event">
<BlueprintViewer title="Record a match event" src="/blueprints/crossplay-telemetry-event.txt" />
  </AccordionItem>
  <AccordionItem title="Record match duration">
<BlueprintViewer title="Record match duration" src="/blueprints/crossplay-telemetry-metric.txt" />
  </AccordionItem>
</Accordion>

**C++**

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

```cpp
#include "Engine/GameInstance.h"
#include "Telemetry/UCIKTelemetrySubsystem.h"
#include "UCIKCore.h"

void SaveTelemetryChoice(UGameInstance &GameInstance, bool PlayerAgreed)
{
    if (auto *Telemetry = GameInstance.GetSubsystem<UUCIKTelemetrySubsystem>())
        Telemetry->SetConsent(PlayerAgreed);
}

bool NeedsTelemetryChoice(UGameInstance &GameInstance)
{
    const auto *Telemetry = GameInstance.GetSubsystem<UUCIKTelemetrySubsystem>();
    return Telemetry && !Telemetry->HasAnsweredConsent();
}

void RecordMatchStarted(UGameInstance &GameInstance)
{
    auto *Telemetry = GameInstance.GetSubsystem<UUCIKTelemetrySubsystem>();
    if (!Telemetry || !Telemetry->HasConsent())
        return;
    const FBetideError Error =
        Telemetry->RecordEvent(TEXT("MatchStarted"), TEXT("{\"mode\":\"Coop\"}"), false);
    if (Error.IsError())
    {
        UE_LOG(LogUCIK, Warning, TEXT("%s"), *Error.GetLogString());
    }
}
```

```cpp
void RecordMatchDuration(UGameInstance &GameInstance, double DurationSeconds)
{
    auto *Telemetry = GameInstance.GetSubsystem<UUCIKTelemetrySubsystem>();
    if (!Telemetry || !Telemetry->HasConsent())
        return;
    const FBetideError Error =
        Telemetry->RecordMetric(TEXT("MatchDurationSeconds"), DurationSeconds, {});
    if (Error.IsError())
        UE_LOG(LogUCIK, Warning, TEXT("%s"), *Error.GetLogString());
}
```

The personal-data flag describes the payload; it does not remove personal data. Avoid putting tokens, credentials, or unnecessary player details in event JSON.

## Flush queued records

**Blueprint**

<BlueprintViewer title="Flush queued records" src="/blueprints/crossplay-telemetry-flush.txt" />

**C++**

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

```cpp
#include "Engine/GameInstance.h"
#include "Telemetry/UCIKTelemetrySubsystem.h"
#include "UCIKCore.h"

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

    Service->Flush(
        FBetideCallback<bool>::CreateWeakLambda(&GameInstance, [](const TBetideResult<bool> &Result) {
            if (!Result.bOk)
            {
                UE_LOG(LogUCIK, Warning, TEXT("%s"), *Result.Error.GetLogString());
                return;
            }
            UE_LOG(LogUCIK, Log, TEXT("Telemetry flushed: %d"), Result.GetValue());
        }));
}
```

Recording can queue a provider upload. Flush at a suitable checkpoint and handle failure; do not assume an asynchronous request finishes after the process exits. Revoking consent stops new records. Without consent, records are dropped rather than saved for later consent.
