Rewards

The Rewards system is the foundation of player progression and engagement in Hiro. Use it to grant game resources when players take certain actions or reach various milestones. This guide defines key concepts, shows how to route rewards (player, team, or mailbox), lists the resource types you can grant, and explains different ways to select rewards (guaranteed, weighted, gacha, or deferred).

Key terms #

  • Reward: A collection of resources, such as currencies, items, and energies, that can be granted to players, teams, or both.
  • Mailbox: A holding area, like an inbox, where rewards can be claimed later.
  • Grant: The action that applies the reward to the target’s wallet, inventory, mailbox, or other storage.
  • Modifier: A temporary effect that changes how future rewards are calculated (for example, “2x coins for 1 hour”).

Integrating rewards #

Rewards are deeply integrated with many Hiro systems:

Player progression #

  • Achievements: Grant rewards when players complete goals or reach certain milestones.
  • Unlockables: Reward players when they unlock new content.
  • Energy: Reward players for spending energy.

Economy #

  • Virtual store: Grant rewards from purchases or apply reward modifiers during transactions.
  • Inventory: Provide rewards when players use items.
  • Donations: Enable player-to-player contributions and rewards.

Player engagement #

  • Event leaderboards: Provide competitive rewards based on player or team rankings.
  • Incentives: Drive behaviors through targeted campaign, like player referral events.

Teams #

Many Hiro systems that operate at the player level also exist at the team level. To learn more, see Teams.

Setting reward destinations #

Rewards can be delivered to one or more destinations:

  • Directly to the player or their mailbox
  • Directly to a team or their shared mailbox
  • Directly to members of a team or their mailboxes
Note: In the following examples, rewards are embedded in the system where they’re used (for example, within an Unlockables config).

Player rewards #

Grant rewards to an individual player. If you set to_mailbox_expiry_sec, the reward is sent to the player mailbox and expires after the configured time. In this example, a reward is granted directly to the player for reaching a major level milestone:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
{
  "achievements": {
    "max_level_achievement": {
      "auto_claim": true,
      "category": "levels",
      "count": 0,
      "max_count": 1,
      "description": "Player reached max level",
      "name": "max_level",
      "reward": {
        "guaranteed": {
          "currencies": {
            "coins": {
              "min": 100,
              "max": 100,
              "multiple": 1
            }
          },
          "items": {
            "stamina_potion": {
              "min": 1,
              "max": 1,
              "multiple": 1
            }
          }
        }
      }
    }
  }
}

Team rewards #

Grant rewards to the team. If you set to_mailbox_expiry_sec, the reward is sent to the team mailbox and expires after the configured time. In this example, a reward is delivered to the team mailbox for winning a tournament:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
{
  "achievements": {
    "team_tournament_winner": {
      "auto_claim": true,
      "category": "team_events",
      "count": 0,
      "max_count": 1,
      "description": "Team won the tournament",
      "name": "tournament_champion",
      "reward": {
        "team_reward": {
          "guaranteed": {
            "currencies": {
              "team_coins": {
                "min": 1000,
                "max": 1000,
                "multiple": 1
              }
            },
            "items": {
              "team_gold_trophy": {
                "min": 1,
                "max": 1,
                "multiple": 1
              }
            }
          },
          "to_mailbox_expiry_sec": 86400
        }
      }
    }
  }
}

Team member rewards #

In addition to team rewards, you may distribute a reward to each team member. Add a member_reward object with its own parameters:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
{
  // ... other configuration options
  "team_reward": {
    "guaranteed": {
      "currencies": {
        "team_coins": {
          "min": 1000,
          "max": 1000,
          "multiple": 1
        }
      }
    },
    "to_mailbox_expiry_sec": 86400,
    "member_reward": {
      "guaranteed": {
        "currencies": {
          "coins": {
            "min": 100,
            "max": 100,
            "multiple": 1
          }
        }
      }
    }
  }
}

Combined rewards #

Combine multiple destinations in one reward so the triggering player, the team, and team members all benefit:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
{
  "achievements": {
    "tournament_champion": {
      "auto_claim": true,
      "category": "events",
      "count": 0,
      "max_count": 1,
      "description": "Won the championship tournament",
      "name": "tournament_champion",
      "reward": {
        "guaranteed": {
          "currencies": {
            "coins": {
              "min": 200,
              "max": 200,
              "multiple": 1
            }
          },
          "items": {
            "champion_badge": {
              "min": 1,
              "max": 1,
              "multiple": 1
            }
          }
        },
        "team_reward": {
          "guaranteed": {
            "currencies": {
              "team_funds": {
                "min": 500,
                "max": 500,
                "multiple": 1
              }
            },
            "items": {
              "team_trophy": {
                "min": 1,
                "max": 1,
                "multiple": 1
              }
            }
          },
          "to_mailbox_expiry_sec": 604800,
          "member_reward": {
            "guaranteed": {
              "currencies": {
                "coins": {
                  "min": 50,
                  "max": 50,
                  "multiple": 1
                }
              },
              "items": {
                "participation_medal": {
                  "min": 1,
                  "max": 1,
                  "multiple": 1
                }
              }
            }
          }
        }
      }
    }
  }
}

This example demonstrates:

1. Individual player rewards

  • 200 coins and 1 champion badge go directly to the triggering player.
  • These are granted immediately because auto_claim is true and no mailbox expiry is set.

2. Team rewards

  • 500 team funds and 1 team trophy go to the team’s shared resources.
  • These are sent to the team mailbox (expires in 7 days = 604,800 seconds).

3. Team member rewards

  • 50 coins and 1 participation medal go to each team member.
  • These are applied directly to each member’s account.

Reward types #

Rewards consist of one or more resource types for both players and teams. Consuming a resource can grant another (for example, using an item can boost currency gain or restore energy).

Currencies #

Virtual money with built‑in validation and controls—for example, coins, premium gems, or team funds:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
{
  "currencies": {
    "coins": {
      "min": 100,
      "max": 500,
      "multiple": 50
    },
    "gems": {
      "min": 1,
      "max": 3
    }
  }
}

Learn more about currencies.

Items #

In‑game objects with optional metadata. Items can be stackable or unique:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
{
  "items": {
    "magic_sword": {
      "min": 1,
      "numeric_properties": {
        "damage": {
          "min": 10,
          "max": 50
        },
        "durability": {
          "min": 80,
          "max": 100
        }
      },
      "string_properties": {
        "element": {
          "options": {
            "fire": {
              "weight": 33
            },
            "ice": {
              "weight": 33
            },
            "lightning": {
              "weight": 34
            }
          }
        }
      }
    }
  }
}

Learn more about inventory items.

Item sets #

Select items from predefined categories using set intersection logic:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
{
  "item_sets": [
    {
      "set": ["rare", "weapon"],
      "min": 1,
      "max": 3,
      "max_repeats": 1
    }
  ]
}

This configuration:

  1. Finds items tagged both “rare” and “weapon”.
  2. Randomly selects 1–3 items.
  3. Ensures no item repeats (max_repeats: 1).
  4. Selects only items available to the player.

Learn more about items sets.

Energy #

Auto‑regenerating resources that power gameplay actions (often called stamina):

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
{
  "energies": {
    "stamina": {
      "min": 20,
      "max": 50
    },
    "mana": {
      "min": 100
    }
  }
}

Learn more about energy.

Energy modifiers #

Temporary effects that change how energy systems behave (for example, faster regen or higher max capacity):

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
{
  "energy_modifiers": [
    {
      "id": "stamina",
      "operator": "multiplier",
      "value": {
        "min": 2
      },
      "duration_sec": 3600
    }
  ]
}

Learn more about energy modifiers.

Reward modifiers #

Temporary buffs that enhance future reward calculations. They stack with other modifiers of the same type and expire automatically:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
{
  "reward_modifiers": [
    {
      "id": "coins",
      "type": "currency",
      "operator": "multiplier",
      "value": {
        "min": 2,
        "max": 3,
        "multiple": 1 // // A number that the value must be a multiple of
      },
      "duration_sec": 7200
    },
    {
      "id": "sword",
      "type": "item",
      "operator": "addition",
      "value": {
        "min": 1,
        "max": 1,
        "multiple": 1 // A number that the value must be a multiple of
      },
      "duration_sec": 3600
    }
  ]
}

This configuration grants two temporary buffs:

  1. Coin multiplier: For the next two hours, all coin rewards are doubled or tripled (randomly selected).
  2. Sword bonus: For the next hour, all sword rewards get +1 additional sword.

Reward selection methods #

Configure how a reward should be selected. The system supports simple, weighted, gacha‑style, deferred (mailbox), and custom reward mechanics.

Simple rewards #

Straightforward rewards that provide immediate gratification and clear feedback.

Example: Daily login bonus

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
{
  "guaranteed": {
    "currencies": {
      "coins": {
        "min": 100,
        "max": 500,
        "multiple": 50
      }
    },
    "items": {
      "login_streak_token": {
        "min": 1
      }
    }
  }
}

Weighted rewards #

Probability‑based rewards that introduce excitement through rarity.

Example: Level-up chest

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
{
  "guaranteed": {
    "items": {
      "health_potion": {
        "min": 1
      }
    }
  },
  "weighted": [
    {
      "items": {
        "rare_gem": {
          "min": 1
        }
      },
      "weight": 25
    }
  ],
  "max_rolls": 1,
  "max_repeat_rolls": 2,
  "total_weight": 100
}

Gacha-style rewards #

Multi‑roll systems with probability mechanics for collection gameplay.

Example: Character summoning

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
{
  "weighted": [
    {
      "items": { "common_hero": { "min": 1 } },
      "weight": 65
    },
    {
      "items": { "rare_hero": { "min": 1 } },
      "weight": 30
    },
    {
      "items": { "legendary_hero": { "min": 1 } },
      "weight": 5
    }
  ],
  "max_rolls": 10,
  "max_repeat_rolls": 3,
  "total_weight": 100
}

Deferred rewards (mailbox) #

Send rewards to a mailbox to claim later. Useful for controlled distribution and admin oversight.

Example: Gift from game admins

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
{
  "guaranteed": {
    "currencies": {
      "compensation_coins": {
        "min": 1000
      }
    },
    "items": {
      "apology_gift": {
        "min": 1
      }
    }
  },
  "to_mailbox": true
}

Custom rewards #

Customize outcomes with hooks that modify reward objects based on game state and player context. This option allows you to modify reward behavior and provide a personalized experience for your players.

Example: Item consumption rewards

The following example demonstrates how to implement a custom reward handler when an inventory item is consumed using the inventorySystem.SetOnConsumeReward() hook:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
func onConsumeReward(ctx context.Context, logger runtime.Logger, nk runtime.NakamaModule, userID, sourceID string, source *hiro.InventoryConfigItem, rewardConfig *hiro.EconomyConfigReward, reward *hiro.Reward) (*hiro.Reward, error) {
  // Perform custom logic or checks based on the specific item being consumed
  switch source.Name {
  case "New Adventurer Chest":
    // Get the player level (assumes a function exists called getPlayerLevel)
    playerLevel := getPlayerLevel(userID)

    // Do nothing if the player isn't <= level 10
    if playerLevel > 10 {
      return nil, runtime.NewError("player level too high to open chest", 13)
    }

    // Define an array of possible items
    possibleItems := []string{"minor_potion", "leather_scraps", "copper_ore"}

    // Choose a random item from the array
    chosenItem := possibleItems[rand.Intn(len(possibleItems))]

    // Reward 2 * playerLevel of the chosen item
    reward.Items = make(map[string]int64)
    reward.Items[chosenItem] = int64(2 * playerLevel)

    // Reward a fixed amount of gold
    reward.Currencies = make(map[string]int64)
    reward.Currencies["gold"] = 500
  case "Home Teleport Stone":
    // Get the player's home location (assumes a function exists called getPlayerHomeLocation)
    playerHomeLocation := getPlayerHomeLocation(userID)

    // Teleport the player home (assumes a function exists called setPlayerLocation)
    setPlayerLocation(userID, playerHomeLocation)
  }

  // Return the modified reward object
  return reward, nil
}

Reference: Inventory API

Assign the hook function in the inventory system:

1
inventorySystem.SetOnConsumeReward(onConsumeReward)

Within the onConsumeReward() function, you have the opportunity to modify the reward object and perform any necessary actions or checks. In the example above, the context of the consumed item is checked and a specific action is taken and reward given depending on the user’s contextual information.

By utilizing a reward hook function and customizing it to fit your game’s specific needs, you can dynamically adjust rewards, incorporate game mechanics, and create a more personalized and engaging experience for your players.

Example: Pity system

The following example demonstrates an implementation of a “pity system” i.e., a way to guarantee rewards to a player after repeated failed attempts to roll in a gacha-style system. Using a custom reward hook on a Virtual Store Purchase, the player is guaranteed to obtain a legendary hero after a certain number of misses.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
const (
    maxPityCounter        = 10
    legendaryItem         = "legendary_sword"
    pityCounterCollection = "pity"
    pityCounterKey        = "pity_counter"
)

type pityStorage struct {
    pity int `json:"pity"`
}

func onPurchaseReward(ctx context.Context, logger runtime.Logger, nk runtime.NakamaModule, userID string, source *hiro.EconomyConfigStoreItem, rewardConfig *hiro.EconomyConfigReward, reward *hiro.Reward) (*hiro.Reward, error) {
  // Don't modify the reward if it isn't the gacha lootbox
  if source.Name != "Gacha Lootbox" {
    return reward, nil
  }

  // Assume the player's pity is 0 by default
  pityCounter := 0

  // If the reward does not contain the legendary sword, increment the player's pity counter
  if _, ok := reward.Items[legendaryItem]; !ok {
    // Get player's current pity from storage
    records, err := nk.StorageRead(ctx, []*runtime.StorageRead{
      {
        Collection: pityCounterCollection,
        Key:        pityCounterKey,
        UserID:     userID,
      },
    })
    if err != nil {
      return nil, err
    }

    // Deserialize the pity storage object
    if len(records) > 0 {
      var pityStorage *pityStorage
      err = json.Unmarshal([]byte(records[0].Value), &pityStorage)
      if err != nil {
        return nil, err
      }

      pityCounter = pityStorage.pity
    }

    // Increase the pity counter
    pityCounter++

    // If the pity counter is over the threshold, replace the reward with the legendary item and reset the pity counter
    if pityCounter >= maxPityCounter {
      reward.Items = make(map[string]int64)
      reward.Items[legendaryItem] = 1
      pityCounter = 0
    }
  }

  // Write the updated pity counter to storage
  pityStorage := &pityStorage{pity: pityCounter}

  // Serialize the pity data to JSON
  bytes, err := json.Marshal(pityStorage)
  if err != nil {
    return nil, err
  }

  // Write the updated pity counter to storage
  if _, err = nk.StorageWrite(ctx, []*runtime.StorageWrite{
    {
      Collection:      pityCounterCollection,
      Key:             pityCounterKey,
      UserID:          userID,
      Value:           string(bytes),
      PermissionRead:  0,
      PermissionWrite: 0,
    },
  }); err != nil {
    return nil, err
  }

  // Return the modified reward object
  return reward, nil
}

Configuring rewards #

The following JSON represents the customization parameters you can use to configure the default user experience for a particular reward.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
{
  "rewards": {
    "guaranteed": {
      "items": {
        "bronze_sword": {
          "min": 1
        },
        "small_potion": {
          "min": 1,
          "max": 5
        }
      },
      "item_sets": [
        {
          "set": ["rare", "weapon"],
          "max_repeats": 0,
          "min": 1
        }
      ],
      "currencies": {
        "coins": {
          "min": 100,
          "max": 500,
          "multiple": 50
        }
      },
      "energies": {
        "power": {
          "min": 100,
          "max": 200
        }
      },
      "energy_modifiers": [
        {
          "id": "power",
          "operator": "multiplier",
          "value": {
            "min": 2
          },
          "duration_sec": 64000
        }
      ],
      "reward_modifiers": [
        {
          "id": "coins",
          "type": "currency",
          "value": {
            "min": 2
          },
          "duration_sec": 32000
        }
      ]
    },
    "weighted": [
      {
        "items": {
          "apple": {
            "min": 1
          }
        },
        "weight": 99
      },
      {
        "items": {
          "legendary_sword": {
            "min": 1
          },
          "weight": 1
        }
      }
    ],
    "max_rolls": 1,
    "max_repeat_rolls": 0,
    "total_weight": 100
  }
}

The JSON schema defines a reward object which defines the various items, item sets, currencies, energies, energy modifiers, and reward modifiers that will be available as rewards where this particular reward is defined. You can configure as few or as many of each as needed for your desired gameplay. This object is defined inline throughout the various data definitions within Hiro. In this example, it is shown as part of a rewards field, but in some systems this field may be named differently (e.g. contributorRewards in the Donations System).

A Reward object consists of the following properties:

Reward #

PropertyTypeDescription
guaranteedRewardContentsThe rewards that a user is guaranteed to receive.
weighted[]RewardContentsThe rewards that a user may or may not receive depending on the outcome of a random roll.
max_rollsint64The number of weighted RewardContents to select from among the possibilities.
max_repeat_rollsint64The maximum number of repeats for any given weighted reward.
total_weightint64The total weight of all weighted reward contents, this can be omitted if you would prefer it to be automatically calculated. If you want a chance for the player to receive no reward, you can manually set this to be higher than the sum of individual weights.

Reward contents #

PropertyTypeDescription
itemsstring:RewardItemA map of item IDs and quantities to reward the player with.
item_sets[]RewardItemSetThe item sets that are part of the reward.
currenciesstring:RewardRangeInt64A map of currency IDs and quantities to reward the player with.
energiesstring:RewardRangeInt32A map of energy IDs and quantities to reward the player with.
energy_modifiers[]RewardEnergyModifierThe energy modifiers that are part of the reward.
reward_modifiers[]RewardRewardModifierThe reward modifiers that are part of the reward.
weightint64The weight of this reward as it relates to the rest of the weighted rewards.

Reward energy modifier #

PropertyTypeDescription
idstringThe ID of the energy that should be modified.
operatorstringThe operation that should be performed on the energy when rewarded (e.g. multiplier, addition).
valueRewardRangeInt64The modifier value range.
duration_secRewardRangeUInt64The duration range for the modifier (in seconds).

Reward reward modifier #

PropertyTypeDescription
idstringThe ID of the reward element that should be modified (e.g. coins or potion).
typestringThe type of the reward that is to be modified (e.g. item or currency).
operatorstringThe operation that should be performed on the reward when rewarded (e.g. multiplier, addition).
valueRewardRangeInt64The modifier value range.
duration_secRewardRangeUInt64The duration range for the modifier (in seconds).

Reward item #

PropertyTypeDescription
minint64The minimum amount to reward.
maxint64The maximum amount to reward.
multipleint64The multiple the amount will be rounded to.
string_propertiesstring:RewardStringPropertyAdditional string properties that will be added to the item instance.
numeric_propertiesstring:RewardRangeFloat64Additional numeric properties that will be added to the item instance.

Reward item set #

PropertyTypeDescription
minint64The minimum amount to reward.
maxint64The maximum amount to reward.
multipleint64The multiple the amount will be rounded to.
max_repeatsint64The maximum amount of repeats that can be rewarded from this set.
set[]stringWhich intersection of item sets to reward from.

Reward ranges #

Below are the types that are used when defining ranges across various properties.

Reward range Int32 #

PropertyTypeDescription
minint32The minimum amount to reward.
maxint32The maximum amount to reward.
multipleint32The multiple the amount will be rounded to.

Reward range Int64 #

PropertyTypeDescription
minint64The minimum amount to reward.
maxint64The maximum amount to reward.
multipleint64The multiple the amount will be rounded to.

Reward range UInt64 #

PropertyTypeDescription
minuint64The minimum amount to reward.
maxuint64The maximum amount to reward.
multipleuint64The multiple the amount will be rounded to.

Reward range Float64 #

PropertyTypeDescription
minfloat64The minimum amount to reward.
maxfloat64The maximum amount to reward.
multiplefloat64The multiple the amount will be rounded to.

Reward string property #

PropertyTypeDescription
total_weightint64The total weight of all weighted property options, this can be omitted if you would prefer it to be automatically calculated. If you want a chance for a property not to be added to the item instance, you can manually set this to be higher than the sum of individual weights.
optionsstring:RewardStringPropertyOptionA map of the possible properties and their weightings.

Reward string property option #

PropertyTypeDescription
weightint64The weight of this property option as it relates to the rest of the weighted property options.