---
title: "Inventory, DLC, and purchases"
description: "Read ownership and manage Steam inventory and purchase results."
seo:
  noindex: true
---

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

Use your game's [Steam App ID](/integrations/steam/setup). Publish its inventory item definitions and configure DLC in Steamworks before testing with an account that owns the relevant content.

## Read inventory

Bind **On Steam Inventory Result Ready** on the **SIK Inventory Subsystem**, then request inventory. Match the returned handle to the handle in the callback. Read items only after a successful result and destroy the handle when finished. Create `InventoryHandle` (SIK Steam Inventory Result) and `InventoryPending` (Boolean, false). The example allows one request at a time; call `StopInventoryQuery` when its owner stops.

**Blueprint**

<BlueprintViewer title="Read inventory and release its result" src="/blueprints/crossplay-steam-inventory.txt" />

**C++**

Game module dependency: `SteamIntegrationKit`.

Put this component in `SteamInventoryExample.h` and attach it to an actor. `Items` holds the returned items; `LastStatus` reports completion.

```cpp
#pragma once

#include "Components/ActorComponent.h"
#include "Engine/GameInstance.h"
#include "Engine/World.h"
#include "Nodes/Inventory/SIK_InventoryLibrary.h"
#include "Nodes/Inventory/SIK_InventorySubsystem.h"
#include "SteamInventoryExample.generated.h"

UCLASS(ClassGroup = (Online), meta = (BlueprintSpawnableComponent))
class USteamInventoryExample : public UActorComponent
{
    GENERATED_BODY()
  public:
    TArray<FSIK_SteamItemDetails> Items;
    TEnumAsByte<ESIK_Result> LastStatus = ResultFail;

    bool Refresh()
    {
        if (bPending || !GetWorld())
            return false;
        auto *GameInstance = GetWorld()->GetGameInstance();
        auto *Inventory =
            GameInstance ? GameInstance->GetSubsystem<USIK_InventorySubsystem>() : nullptr;
        if (!Inventory)
            return false;
        Subsystem = Inventory;
        Inventory->OnSteamInventoryResultReady.AddUniqueDynamic(this, &ThisClass::OnResult);
        bPending = USIK_InventoryLibrary::GetAllItems(Handle);
        if (!bPending)
        {
            LastStatus = ResultFail;
            Stop();
        }
        return bPending;
    }

    void Stop()
    {
        if (bPending)
            USIK_InventoryLibrary::DestroyResult(Handle);
        bPending = false;
        if (auto *Inventory = Subsystem.Get())
            Inventory->OnSteamInventoryResultReady.RemoveDynamic(this, &ThisClass::OnResult);
    }

  protected:
    virtual void EndPlay(const EEndPlayReason::Type Reason) override
    {
        Stop();
        Super::EndPlay(Reason);
    }

  private:
    bool bPending = false;
    FSIK_SteamInventoryResult Handle;
    TWeakObjectPtr<USIK_InventorySubsystem> Subsystem;

    UFUNCTION()
    void OnResult(FSIK_SteamInventoryResult Result, TEnumAsByte<ESIK_Result> Status)
    {
        if (!bPending || Result.Result != Handle.Result)
            return;
        LastStatus = Status;
        Items.Reset();
        if (Status == ResultOK && !USIK_InventoryLibrary::GetResultItems(Handle, Items))
            LastStatus = ResultFail;
        Stop();
    }
};
```

The callback ignores other requests. `Stop()` releases an active handle and removes this listener; call it when cancelling.

`Get All Items` returning true means the request started. **On Steam Inventory Full Update** is an additional freshness notification; it can be absent when inventory has not changed, so do not use it as your only completion signal.

Item **definitions** describe item types; item **instances** are the player's owned stacks. Call **Load SIK Item Definitions**, wait for **On Steam Inventory Definition Update**, then use **Get Item Definition IDs** and **Get SIK Item Definition Property** for names, descriptions, and other metadata. The C++ methods have the same names on `USIK_InventoryLibrary`.

## Buy inventory items

**Blueprint**

<Accordion>
  <AccordionItem title="Read a selected item price">
<BlueprintViewer title="Read a selected item price" src="/blueprints/crossplay-steam-item-price.txt" />
  </AccordionItem>
  <AccordionItem title="Start checkout for one item">
<BlueprintViewer title="Start checkout for one item" src="/blueprints/crossplay-steam-buy-item.txt" />
  </AccordionItem>
</Accordion>

**C++**

```cpp
#include "Nodes/Inventory/SIK_StartPurchase_AsyncFunction.h"
#include "Nodes/Inventory/SIK_RequestPrices_AsyncFunction.h"
#include "Nodes/Inventory/SIK_InventoryLibrary.h"

bool RequestStorePrices(const FScriptDelegate &OnReady, const FScriptDelegate &OnFailed)
{
    auto *Request = USIK_RequestPrices_AsyncFunction::RequestPrices();
    if (!Request)
        return false;
    Request->OnSuccess.Add(OnReady);
    Request->OnFailure.Add(OnFailed);
    Request->Activate();
    return true;
}

bool ReadSelectedItemPrice(FSIK_SteamItemDef Item, int64 &CurrentPrice, int64 &BasePrice)
{
    CurrentPrice = 0;
    BasePrice = 0;
    return USIK_InventoryLibrary::GetItemPrice(Item, CurrentPrice, BasePrice);
}

bool BuyOneSelectedItem(FSIK_SteamItemDef Item, const FScriptDelegate &OnStarted,
                        const FScriptDelegate &OnFailed)
{
    auto *Request = USIK_StartPurchase_AsyncFunction::StartPurchase({Item}, {1});
    if (!Request)
        return false;
    Request->OnSuccess.Add(OnStarted);
    Request->OnFailure.Add(OnFailed);
    Request->Activate();
    return true;
}
```

Price handlers take `(TEnumAsByte<ESIK_Result>, const FString& CurrencyCode)`. Read the selected item price only from the successful price callback. Checkout handlers take `(TEnumAsByte<ESIK_Result>, int64 OrderId, int64 TransactionId)`. Both nodes route Steam errors to `OnFailure`.

Use an item definition published for sale. Request prices before reading them and retain the returned currency code. The example prints raw price values; format them for your store UI. [Steam pricing API](https://partner.steamgames.com/doc/api/ISteamInventory#RequestPrices). Supply one positive quantity per item definition. **Start SIK Purchase** starts checkout; its success is not permission to grant items yourself. After checkout, use [Read inventory](#read-inventory) to observe delivered items; delivery may not be visible immediately. Keep the purchase event on an explicit Buy action, separate from loading prices. Handle cancellation without changing ownership.

## Check DLC

**Blueprint**

<BlueprintViewer title="Install DLC and check availability" src="/blueprints/crossplay-steam-dlc.txt" />

**C++**

```cpp
#include "Nodes/Apps/SIK_AppLibrary.h"

bool CanUseSteamDlc(int32 DlcAppId)
{
    return USIK_AppLibrary::IsDLCInstalled(DlcAppId);
}

void InstallOwnedDlc(int32 DlcAppId)
{
    if (!CanUseSteamDlc(DlcAppId))
        USIK_AppLibrary::InstallDLC(FSIK_AppId(DlcAppId));
}

bool ReadDlcDownload(int32 DlcAppId, int64 &Downloaded, int64 &Total)
{
    Downloaded = 0;
    Total = 0;
    if (CanUseSteamDlc(DlcAppId))
        return false;
    const bool Available =
        USIK_AppLibrary::GetDLCDownloadProgress(FSIK_AppId(DlcAppId), Downloaded, Total);
    if (!Available)
    {
        Downloaded = 0;
        Total = 0;
    }
    return Available;
}
```

Call `CanUseSteamDlc` first on each refresh, then `ReadDlcDownload` if it is still unavailable. False from the progress query means no usable progressâ€”not a completed install.

Call the refresh event from a timer or your download UI. Use your DLCâ€™s App ID, not the base gameâ€™s. **Is SIK DLC Installed** checks both ownership and installation. **Is SIK Subscribed App** checks access to an app, which is a different question. A visible DLC store page is not proof of ownership, and owning DLC does not load its assets into Unreal for you.

<Accordion>
  <AccordionItem title="Consumption, exchanges, and trusted ownership">

**Consume SIK Item**, **Exchange SIK Items**, and **Transfer SIK Item Quantity** return inventory-result handles too: use the same completion and cleanup flow. Consuming an instance reduces its quantity and may remove it. Exchanges must match recipes configured in Steamworks; client calls cannot invent arbitrary rewards.

For inventory sent by another player, deserialize the signed result, check its completion status and freshness, then call **Check SIK Result Steam ID** against the authenticated player. A readable item array alone is insufficient. Destroy that result handle as well. See [Valve's inventory result requirements](https://partner.steamgames.com/doc/api/ISteamInventory#DeserializeResult).

Keep publisher keys and privileged inventory grants on your backend. Validate ownership there for backend-controlled rewards; do not trust client-supplied item IDs or booleans.

  </AccordionItem>
</Accordion>
