Set up rewarded video providers

This guide explains how to integrate ironSource or AppLovin MAX with Hiro’s Economy system for server-side reward verification.

For background on how rewarded video works, see the Rewarded Video.

Before you start #

Before you set up rewarded video providers, ensure:

  • You have a Nakama server with Hiro installed.
  • You have an ironSource or AppLovin MAX account with ad units configured.
  • You have access to your ad network’s private key or event key.

Server setup #

Initialize with ad provider keys #

Pass your ad network private keys as extra parameters to the economy system. These keys verify that webhook callbacks came from the ad network.

main.go
1
2
3
4
5
6
systems, err := hiro.Init(ctx, logger, nk, initializer, binPath, hiroLicense,
    hiro.WithEconomySystem("economy.json", true,
        "your-ironsource-private-key",  // ironSource private key
        "your-applovin-event-key"),     // AppLovin MAX event key
    // ... other systems
)

These extra parameters are:

  1. ironSource private key (find this in your ironSource dashboard)
  2. AppLovin MAX event key

If you only use one ad network, pass an empty string for the other.

Configure webhook URL #

In your ad network dashboard, configure a server-to-server callback URL pointing to your Nakama server:

For IronSrc:

1
2
3
4
5
6
7
https://<your-server>/v2/rpc/RPC_ID_ECONOMY_PLACEMENT_SUCCESS
  ?http_key=<nakama-runtime-http-key>
  &unwrap
  &user_id=[USER_ID]
  &rewards=[REWARDS]
  &event_id=[EVENT_ID]
  &placement_name=[PLACEMENT_NAME]

For AppLovin MAX:

1
2
3
4
5
6
7
8
https://<your-server>/v2/rpc/RPC_ID_ECONOMY_PLACEMENT_SUCCESS
  ?http_key=<nakama-runtime-http-key>
  &unwrap
  &user_id={USER_ID}
  &event_id={EVENT_ID}
  &event_token_all={EVENT_TOKEN_ALL}
  &placement={PLACEMENT}
  &custom_data={CUSTOM_DATA}

Replace:

  • <your-server> with your Nakama server URL
  • <nakama-runtime-http-key> with your HTTP key (found in Nakama Console under Settings > Configuration)

The parameters in brackets (like [USER_ID]) are placeholders that the ad network replaces with actual values when sending the callback. When configuring in your ad network dashboard, enter the URL as a single line.

Define placements #

Add placement definitions to your economy configuration. Each placement specifies the reward granted when a player watches the ad:

economy.json
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
{
  "placements": {
    "bonus_gold": {
      "reward": {
        "guaranteed": {
          "currencies": {
            "gold": {
              "min": 100
            }
          }
        }
      },
      "additional_properties": {
        "adunit_id_ios": "a761a6352f8b9a27",
        "adunit_id_and": "4b8754e43e3fae1b"
      }
    }
  }
}

Use additional_properties to map placements to ad unit IDs, though this isn’t required for the placement to work.

Client setup #

Pass user ID to ad SDK #

Pass your player’s Nakama user ID to the ad network SDK during initialization. This allows the ad network to include the user ID in the webhook callback.

For ironSource/LevelPlay in Unity, pass the user ID as the second parameter when initializing:

GameManager.cs
1
LevelPlay.Init("YourAppKey", session.UserId);

API reference: LevelPlay.Init

Start a placement #

When the player requests to watch an ad, call PlacementStart:

RewardedAdManager.cs
1
var response = await economySystem.PlacementStartAsync("bonus_gold", new Dictionary<string, string>());
You must pass a metadata dictionary even if empty because the API requires it.

The metadata dictionary is preserved through the entire flow and returned when the placement completes. Use this to track context like which screen triggered the ad:

RewardedAdManager.cs
1
2
3
4
5
var metadata = new Dictionary<string, string> {
    { "source", "level_complete_screen" },
    { "level", "42" }
};
var response = await economySystem.PlacementStartAsync("bonus_gold", metadata);

After you start the placement in Hiro, you can now call your provider to show the Rewarded Video Ad. During this process, you also need to pass the Reward ID, which is in the response of PlacementStartAsync, to the platform.

For ironSrc, you need to set this as a MetaData before you start the rewarded video:

1
2
3
4
5
6
var rewardedServerParams = new[]
{
    $"custom_reward_id={response.Result.RewardId}"
};

LevelPlay.SetMetaData("LevelPlay_Rewarded_Server_Params",rewardedServerParams);

For AppLovin MAX, along with the ShowRewardedAd call, you need to pass the Reward ID as a custom data.

1
MaxSdk.ShowRewardedAd(RewardedAdUnitId, "bonus_gold", response.Result.RewardId);

Check placement status #

After the ad finishes, check if the reward was granted. Use PollPlacementStatusAsync for automatic retries:

RewardedAdManager.cs
1
var status = await economySystem.PollPlacementStatusAsync("bonus_gold");

This method automatically retries and triggers the fallback mechanism after 3 unsuccessful checks.

Alternatively, use PlacementStatusAsync for a single check:

RewardedAdManager.cs
1
2
var attemptCount = 1;
var status = await economySystem.PlacementStatusAsync("bonus_gold", rewardId, attemptCount);

When the status indicates the placement is complete, the reward has been granted to the player.

Fallback mechanism #

If the server-to-server callback doesn’t arrive (due to network issues or the player disconnecting), Hiro provides a fallback. When the client polls for placement status, the server grants the reward automatically after 3 unsuccessful checks. This ensures players always receive their reward, though it’s less secure than webhook verification.

The fallback is triggered automatically when using PollPlacementStatusAsync, or when calling PlacementStatusAsync with a retry count of 3 or higher.

Customize rewards #

Use SetOnPlacementReward to modify rewards before they are granted. This hook receives the placement configuration and metadata, allowing you to adjust rewards based on context.

main.go
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
economySystem.SetOnPlacementReward(func(ctx context.Context, logger runtime.Logger, nk runtime.NakamaModule, userID string, info *hiro.EconomyPlacementInfo, reward *hiro.Reward) (*hiro.Reward, error) {
    // Modify reward based on placement info or metadata
    // For example, double rewards for a specific placement
    if info.Metadata["bonus"] == "true" {
        for id, currency := range reward.Currencies {
            reward.Currencies[id] = currency * 2
        }
    }
    return reward, nil
})

The EconomyPlacementInfo struct provides:

  • Placement: The placement configuration from your economy JSON
  • Metadata: The metadata dictionary passed to PlacementStart

See also #