# Set up rewarded video providers

**URL:** https://heroiclabs.com/docs/hiro/guides/gameplay-mechanics/rewarded-video-providers/
**Keywords:** set up rewarded video providers, hiro
**Categories:** hiro, rewarded-video-providers, gameplay-mechanics

---


# 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](/hiro/concepts/economy/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.

{{< code type="server" filename="main.go" hideable=false >}}

```go
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
)
```

{{< /code >}}

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:**
```text
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:** 
```text
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:

{{< code type="server" filename="economy.json" hideable="false" >}}

```json
{
  "placements": {
    "bonus_gold": {
      "reward": {
        "guaranteed": {
          "currencies": {
            "gold": {
              "min": 100
            }
          }
        }
      },
      "additional_properties": {
        "adunit_id_ios": "a761a6352f8b9a27",
        "adunit_id_and": "4b8754e43e3fae1b"
      }
    }
  }
}
```

{{< /code >}}

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:

{{< code type="client" filename="GameManager.cs" hideable="false" >}}

```csharp
LevelPlay.Init("YourAppKey", session.UserId);
```

{{< /code >}}

API reference: [LevelPlay.Init](https://developers.is.com/ironsource-mobile/unity/unity-plugin/#step-2)

### Start a placement

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

{{< code type="client" filename="RewardedAdManager.cs" hideable="false" >}}

```csharp
var response = await economySystem.PlacementStartAsync("bonus_gold", new Dictionary<string, string>());
```

{{< /code >}}

{{< note "important" >}}
You must pass a metadata dictionary even if empty because the API requires it.
{{< /note >}}

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:

{{< code type="client" filename="RewardedAdManager.cs" hideable="false" >}}

```csharp
var metadata = new Dictionary<string, string> {
    { "source", "level_complete_screen" },
    { "level", "42" }
};
var response = await economySystem.PlacementStartAsync("bonus_gold", metadata);
```

{{< /code >}}

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: 
```csharp
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. 
```csharp
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:

{{< code type="client" filename="RewardedAdManager.cs" hideable="false" >}}

```csharp
var status = await economySystem.PollPlacementStatusAsync("bonus_gold");
```

{{< /code >}}

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

Alternatively, use `PlacementStatusAsync` for a single check:

{{< code type="client" filename="RewardedAdManager.cs" hideable="false" >}}

```csharp
var attemptCount = 1;
var status = await economySystem.PlacementStatusAsync("bonus_gold", rewardId, attemptCount);
```

{{< /code >}}

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.

{{< code type="server" filename="main.go" hideable="false" >}}

```go
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
})
```

{{< /code >}}

The `EconomyPlacementInfo` struct provides:

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

## See also

- [Rewarded Video](/hiro/concepts/economy/rewarded-video/)
- [Unity Rewarded Video](/hiro/unity/economy/rewarded-video/)
- [ironSource S2S callbacks](https://developers.is.com/ironsource-mobile/air/server-to-server-callback-setting/)
- [AppLovin MAX S2S callbacks](https://dash.applovin.com/documentation/mediation/s2s-rewarded-callback-api)
