# Rewards

**URL:** https://heroiclabs.com/docs/hiro/concepts/economy/rewards/
**Summary:** The Rewards system is the foundation of player progression and engagement in Hiro, enabling you to grant game resources in response to player actions and accomplishments.
**Keywords:** economy, rewards
**Categories:** hiro, rewards, economy

---

<figure class="float-right" style="max-width: 35%; border: 20px solid transparent; text-align: center;">
  <img 
    src="{{< fingerprint_image "/images/pages/hiro/concepts/economy/rewards-wordscapes.png" >}}" 
    style="width: 100%;"
  >
  <figcaption> Hiro can provide a wide range of reward definitions integrated with the systems - Example from Wordspaces by PeopleFun </figcaption>
</figure>

# 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](../../achievements/): Grant rewards when players complete goals or reach certain milestones.
- [Unlockables](../../unlockables/): Reward players when they unlock new content.
- [Energy](../../energy/): Reward players for spending energy.

#### Economy

- [Virtual store](../virtual-store/): Grant rewards from purchases or apply reward modifiers during transactions.
- [Inventory](../../inventory/): Provide rewards when players use items.
- [Donations](../donations/): Enable player-to-player contributions and rewards.

#### Player engagement

- [Event leaderboards](../../event-leaderboards/): Provide competitive rewards based on player or team rankings.
- [Incentives](../../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](../../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 "important" "" >}}
**Note**: In the following examples, rewards are embedded in the system where they’re used (for example, within an [Unlockables](../../unlockables/) config).
{{< / note >}}

### 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:

```json
{
  "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:

```json
{
  "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:

```json
{
  // ... 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:

```json
{
  "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:

```json
{
  "currencies": {
    "coins": {
      "min": 100,
      "max": 500,
      "multiple": 50
    },
    "gems": {
      "min": 1,
      "max": 3
    }
  }
}
```

Learn more about [currencies](../virtual-currencies/).

### Items

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

```json
{
  "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](../../inventory).

### Item sets

Select items from predefined categories using set intersection logic:

```json
{
  "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 **in this reward** (`max_repeats: 1`).
4. Selects only items available to the player.

Learn more about [items sets](../../inventory#item-sets).

### Energy

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

```json
{
  "energies": {
    "stamina": {
      "min": 20,
      "max": 50
    },
    "mana": {
      "min": 100
    }
  }
}
```

Learn more about [energy](../../energy/).

### Energy modifiers

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

```json
{
  "energy_modifiers": [
    {
      "id": "stamina",
      "operator": "infinite",
      "duration_sec": {
        "min": 21600
      }
    }
  ]
}
```

Learn more about [energy modifiers](../../energy/energy-modifiers/).

### Reward modifiers

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

```json
{
  "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**

```json
{
  "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**

```json
{
  "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**

```json
{
  "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. Set `to_mailbox_expiry_sec` to a large value so the reward stays in the mailbox until it is claimed.

**Example: Gift from game admins**

```json
{
  "guaranteed": {
    "currencies": {
      "compensation_coins": {
        "min": 1000
      }
    },
    "items": {
      "apology_gift": {
        "min": 1
      }
    }
  },
  "to_mailbox_expiry_sec": 99999999,
  "message": "We're sorry for the inconvenience. Here's a gift from the team!"
}
```

### 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:

```go
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](../../../server-framework/inventory/#setonconsumereward)

Assign the hook function in the inventory system:

```go
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](../virtual-store/), the player is guaranteed to obtain a legendary hero after a certain number of misses.

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

```json
{
  "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,
    "message": "Congratulations on completing the event!"
  }
}
```

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](../donations/)).

A `Reward` object consists of the following properties:

### Reward

{{< table name="gdk.concepts.economy.reward" >}}

### Reward contents

{{< table name="gdk.concepts.economy.reward-contents" >}}

### Reward energy modifier

{{< table name="gdk.concepts.economy.reward-energy-modifier" >}}

### Reward reward modifier

{{< table name="gdk.concepts.economy.reward-reward-modifier" >}}

### Reward item

{{< table name="gdk.concepts.economy.reward-item" >}}

### Reward item set

{{< table name="gdk.concepts.economy.reward-item-set" >}}

### Reward ranges

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

#### Reward range Int32

{{< table name="gdk.concepts.economy.reward-range-int32" >}}

#### Reward range Int64

{{< table name="gdk.concepts.economy.reward-range-int64" >}}

#### Reward range UInt64

{{< table name="gdk.concepts.economy.reward-range-uint64" >}}

#### Reward range Float64

{{< table name="gdk.concepts.economy.reward-range-float64" >}}

### Reward string property

{{< table name="gdk.concepts.economy.reward-string-property" >}}

#### Reward string property option

{{< table name="gdk.concepts.economy.reward-string-property-option" >}}
