Rewarding users
Challenges and Battle Pass levels can have rewards. Rewards can be anything, and it depends on your application and business model which rewards make sense to users.
Types of rewards
Overall, rewards can be separated into two basic groups:
Status
In communities, many people are interested in setting themselves apart from the crowd. They want status symbols to express either their personality or to stand out. Many games make use of this human nature and provide rewards like:
- Fashion - skins, cloths, etc. (Fortnite)
- Badges (Web communities)
In most (multiplayer) games and communities rewards don’t have any influence on the gameplay at all. It’s all purely esthetics nature.
Utility
Depending on the type of game rewards can also unlock items that change gameplay or unlock new features within your application or game. If your business logic has some quoters, you can reward users by pushing those quoters. This is often a very cheap solution as most users don’t make use of their volumes, so giving them more of that feels rewarding.
A few examples of utility rewards:
- More storage
- Special items
- Secret levels
- New features
- Coins
Rewards in SCILL
Implementing rewards is up to you, SCILL does not provide any APIs on this topic, as rewards are so tightly integrated into your business or game logic. However, SCILL handles the state process, i.e. it will keep track of users challenges and levels and will notify you once a user wants to claim a reward.
Setting rewards in Admin Panel
In the Admin Panel you can set a reward type and a value for challenges and battle passes. This looks like this:

Reward settings in the Admin Panel
We use the same UI in Admin Panel for challenges and battle passes, however, the data structures are slightly different in naming.
The Challenge object has these properties:
challenge_reward string
Set a reward for this challenge. This is a string value that you can map to anything in your code. Use in combination with challenge_reward_type.
challenge_reward_type string
The reward type can be set to various different settings. Use it to implement different reward types on your side and use challenge_reward to set the value or amount of this reward.
In the BattlePassLevel object has these properties:
reward_amount string
In the Admin Panel you can set different types of rewards. You can also set an identifier of an in-game-item or anything you like. Use this to include the reward into your own business logic.
reward_type_name string
There are different types of rewards available. Possible values are Coins, Voucher, Money and Experience.
In Admin Panel you can choose the reward_type
from a list which offers these values:
- Nothing
- Coins
- Money
- Experience
- Item
There is no logic behind these values, you can use them as you like. Please let us know if you need more options that fit your business logic. We want to keep it as short as possible, however, they should also help documenting your code and it’s also easy to see in Admin Panel.
As reward_amount
you can set a string. You can use that to identify the reward in your own business logic or game. For
example if you want to unlock a skin you could set the value to BlueJacketWithGold
. This can also be the IDs in your
database, etc.
Triggers
SCILL implements different triggers to inform your application or backend once it’s time to unlock a reward to your user.
- Realtime Update Notifications
- In your client you listen to notifications that will be sent by the SCILL backend. Our SDKs offer a simple, often one line of code solution to listen on notifications. You just need to call a function and provide a callback function which will be called whenever a new message is sent.
- Webhooks
- You can create a webservice/endpoint in your backend and set a Webhook URL in Admin Panel for Challenges and Battle Passes which is called whenever something happens in the SCILL backend.
It depends on the architecture of your application which way is best to implement. In a single player mobile application without any servers, it makes sense to implement the Realtime Update API into your client and unlock items or coins directly in the client.
In a multiplayer game with user accounts, you might have a single source of trust for all your users like a users database. In this case, it’s better to implement a Webhook making it much harder to cheat or hack the system as everything is done purely in the backend. Use the Webhook system for this.
Unlocking rewards
The Realtime Update Notification system, and the Webhook system share the same data structures, but they are a bit different as shown above between Challenges and Battle Passes. However, the concept is the same for both.
Challenges
The payload you receive in a real time update message, or the webhook looks like this. Please note, that we have removed some properties so that it is easier to see what is important for this topic. The whole payload can be seen in the Playground application or this documentation: ChallengeWebhookPayload.
{
"new_challenge": {
...
"challenge_reward": "5GB",
"challenge_reward_type": "Item",
"challenge_type": "user-invite",
"type": "finished",
"user_id": "1234"
},
"old_challenge": {
...
"challenge_reward": "5GB",
"challenge_reward_type": "Item",
"challenge_type": "user-invite",
"type": "unclaimed",
"user_id": "1234"
}
}
As you can see, in each notification you receive the state of the challenge before the change and after the change. In this
case we are only interested in the type
property. If it changed from in-progress
to finished
then the challenge
has been rewarded.
Here is some example code for you to get started quickly with rewarding users if they achieved challenges.
// ... accessToken generated before ...
var scillClient = new SCILLClient(accessToken, AppId);
scillClient.StartChallengeUpdateNotifications(payload => {
// Important: This will be called from a worker thread. If you need to access other APIs you might need to dispatch
// code on the "main thread". See SCILLThreadSafety class in Unity SDK to understand how to do that in Unity.
if (payload.old_challenge.type === "unclaimed" && payload.new_challenge.type === "finished") {
// A challenge reward has been claimed
if (challenge.challenge_reward_type === "Item") {
// For example call an InventoryManager instance to unlock the item defined in Admin Panel
InventoryManager.UnlockItem(challenge.challenge_reward_amount);
}
}
});
const SCILL = require("@scillgame/scill-js");
// ... accessToken being generated before...
const monitor = SCILL.startMonitorChallengeUpdates(accessToken, (payload) => {
if (payload.old_challenge.type === 'unclaimed' && payload.new_challenge.type === 'finished') {
// A challenge reward has been claimed
if (payload.new_challenge.challenge_reward_type === 'Item') {
unlockItem(payload.new_challenge.challenge_reward_amount);
}
}
});
function unlockItem(itemId) {
// Do whatever is required to unlock the itemId for the user, i.e.
}
If you want to handle rewards in your backend, you could do it this way - an example backend implementation with NodeJS:
// This example implements a NodeJS backend listening on webhook requests from SCILL to unlock items for users
const express = require('express');
const bodyParser = require('body-parser');
const scill = require('@scillgame/scill-js');
// Generate an instance of the SCILL SDK with example API-Key
const auth = scill.getAuthApi('__YOUR_API_KEY__');
const app = express();
const port = 80;
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: false }));
unlockItem = function(itemId, userId) {
// Unlock an item, for example enter a row in a database, etc.
}
// Implement a route that you can set in Admin Panel. This is what you would have to set in the admin panel:
// https://www.yourdomain.com/scill/webhook/challenges
// When creating the webhook a secret will be created by SCILL and will be sent as a GET parameter. Use the secret
// to make sure nobody can call your webhook without permission.
app.post('/scill/webhook/challenges', (req, res) => {
const data = req.body;
let secret = req.query.secret;
if (!secret || secret !== "__YOUR_WEBHOOK_SECRET__") {
return res.status(401).send({'error': 'You dont have permission to call this route'});
}
// If payload is available
if (data) {
// Check if challenge has been claimed
if (payload.old_challenge.type === 'unclaimed' && payload.new_challenge.type === 'finished') {
// A challenge reward has been claimed
if (payload.new_challenge.challenge_reward_type === 'Item') {
unlockItem(payload.new_challenge.challenge_reward_amount, payload.new_challenge.user_id);
}
} else {
// Challenge action like unlock, activate, progress, etc.
}
}
// Return a 200 status code so that SCILL knows everything went fine
res.send({message: 'OK'});
});
app.listen(port, () => {
console.log('Example app listening at http://localhost:${port}');
});
Battle Passes
Battle Passes don’t have rewards for single challenges, but for levels. For each level a reward type and reward value can be set like in the Challenges system.
As battle passes are more complex products, this system sends three different types of notifications:
As you might have guessed already, we are interested in that battlepass-level-reward-claimed
notification to reward
users. The same as for challenges apply here. Either listen on the realtime notifications or implement a webservice
for example in a cloud function that you set as a Webhook in Admin Panel.
The payload you receive in this notification looks like this:
{
"webhook_type": "battlepass-level-reward-claimed",
"battle_pass_level_reward_claimed": {
"app_id": "597737952688570369",
"battle_pass_id": "603693723277918210",
"level_id": "563006391671062538",
"user_id": "d290f1ee-6c54-4b01-90e6-d701748f0851",
"level_position_index": 0,
"reward_amount": "100",
"reward_type_name": "Coins"
}
}
Some example code to get you started using Realtime Notifications System:
// ... accessToken generated before ...
var scillClient = new SCILLClient(accessToken, AppId);
scillClient.StartBattlePassUpdateNotifications(battlePassId, payload => {
// Important: This will be called from a worker thread. If you need to access other APIs you might need to dispatch
// code on the "main thread". See SCILLThreadSafety class in Unity SDK to understand how to do that in Unity.
if (payload.webhook_type === "battlepass-level-reward-claimed") {
if (payload.battlepass_level_reward_claimed.reward_type_name === "Item") {
InventoryManager.UnlockItem(payload.battlepass_level_reward_claimed.reward_amount);
}
}
});
const SCILL = require("@scillgame/scill-js");
// ... accessToken being generated before...
const monitor = SCILL.startMonitorBattlePassUpdates(accessToken, battlePass.battle_pass_id, (payload) => {
if (payload.webhook_type === 'battlepass-level-reward-claimed') {
if (payload.battlepass_level_reward_claimed.reward_type_name === 'Item') {
unlockItem(payload.battlepass_level_reward_claimed.reward_amount);
}
}
});
function unlockItem(itemId) {
// Do whatever is required to unlock the itemId for the user, i.e.
}
And this is some NodeJS example code on how to handle battle pass rewards in backend:
// This example implements a NodeJS backend listening on webhook requests from SCILL to unlock items for users
const express = require('express');
const bodyParser = require('body-parser');
const scill = require('@scillgame/scill-js');
// Generate an instance of the SCILL SDK with example API-Key
const auth = scill.getAuthApi('__YOUR_API_KEY__');
const app = express();
const port = 80;
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: false }));
unlockItem = function(itemId, userId) {
// Unlock an item, for example enter a row in a database, etc.
}
// Implement a route that you can set in Admin Panel. This is what you would have to set in the admin panel:
// https://www.yourdomain.com/scill/webhook/challenges
// When creating the webhook a secret will be created by SCILL and will be sent as a GET parameter. Use the secret
// to make sure nobody can call your webhook without permission.
app.post('/scill/webhook/challenges', (req, res) => {
const data = req.body;
let secret = req.query.secret;
if (!secret || secret !== "__YOUR_WEBHOOK_SECRET__") {
return res.status(401).send({'error': 'You dont have permission to call this route'});
}
// If payload is available
if (data) {
// Check if battle pass level reward has been claimed
if (payload.webhook_type === 'battlepass-level-reward-claimed') {
if (payload.battlepass_level_reward_claimed.reward_type_name === 'Item') {
unlockItem(payload.battlepass_level_reward_claimed.reward_amount);
}
}
}
// Return a 200 status code so that SCILL knows everything went fine
res.send({message: 'OK'});
});
app.listen(port, () => {
console.log('Example app listening at http://localhost:${port}');
});