Player
Player identification
On the Jest platform, each user has a unique playerId per game. This identifier remains the same if a player starts as a guest and later registers, allowing you to track player progress and store player data.
Guest users can be prompted to register or sign in on the Jest platform. See Platform login for more information.
Registered users can be re-engaged via notifications. See Notifications for more information.
JestSDK.getPlayer()
Returns the current player identifier and indicates whether the player is registered or playing as a guest.
JestSDK.getPlayer(): {
playerId: string;
registered: boolean;
};
Example:
const player = JestSDK.getPlayer();
if (!player.registered) {
// Guest player: notifications cannot be scheduled
// Consider prompting the player to register
} else {
JestSDK.notifications.scheduleNotification({
/*...*/
});
}
JestSDK.getPlayerSigned()
If your game has a backend, use this method to obtain a signed payload that can be sent to your server to authenticate the player and attach to authenticated requests.
This works for both registered players and guests. For guests, player.registered is false.
JestSDK.getPlayerSigned(): Promise<{
player: {
playerId: string;
registered: boolean;
};
playerSigned: string; // JWS with the same `player` object plus iat, aud, sub
}>;
The returned playerSigned value is a JSON Web Signature (JWS) signed using your game’s shared secret with the HS-256 algorithm. You can use any standard JWS/JWT library on your backend to verify the signature and extract the player information.
The shared secret shown in the Developer Console is base64-encoded. Most JWT libraries expect the decoded key when verifying the signature.
The signed payload includes an issued-at (iat) timestamp that can be used to verify token freshness. Jest does not set an explicit expiration time; you may reject tokens older than a chosen threshold and have the client request a new token.
The verified payload has this shape:
type PlayerSignedPayload = {
player: {
playerId: string;
registered: boolean;
};
iat: number;
aud: string; // game id
sub: string; // player id
};
Examples for popular backend languages, including signature validation and freshness checks (for example, rejecting tokens older than 24 hours), are provided below.
Example server code
- Node.js
- PHP
- Ruby
- Python
- Java
- C#
// npm i jsonwebtoken
import jwt from "jsonwebtoken";
export function verifyPlayerSigned(token: string) {
const secretBase64 = process.env.JWS_SECRET!;
const secret = Buffer.from(secretBase64, "base64");
try {
// IMPORTANT: lock the allowed algs
const payload = jwt.verify(token, secret, {
algorithms: ["HS256"],
}) as jwt.JwtPayload;
const iat = payload.iat as number;
const maxAgeSeconds = 24 * 60 * 60;
const nowSeconds = Math.floor(Date.now() / 1000);
if (nowSeconds - iat > maxAgeSeconds) {
throw new Error("Token too old");
}
const player = payload.player as
| {
playerId: string;
registered: boolean;
}
| undefined;
if (!player) {
throw new Error("Missing player payload");
}
console.log("playerId:", player.playerId);
console.log("registered:", player.registered);
return payload;
} catch (err) {
console.error("Invalid token:", err);
throw err;
}
}
<?php
// composer require firebase/php-jwt
require "vendor/autoload.php";
use Firebase\JWT\JWT;
use Firebase\JWT\Key;
function verifyPlayerSigned(string $token): object
{
$secretBase64 = getenv("JWS_SECRET");
if ($secretBase64 === false) {
throw new RuntimeException("Missing JWS_SECRET");
}
$key = base64_decode($secretBase64, true);
if ($key === false) {
throw new RuntimeException("Invalid base64 in JWS_SECRET");
}
$payload = JWT::decode($token, new Key($key, "HS256"));
$iat = $payload->iat;
$maxAgeSeconds = 24 * 60 * 60;
if (time() - (int)$iat > $maxAgeSeconds) {
throw new RuntimeException("Token too old");
}
if (!isset($payload->player) || !is_object($payload->player)) {
throw new RuntimeException("Missing player payload");
}
echo "playerId: {$payload->player->playerId}\n";
echo "registered: " . ($payload->player->registered ? "true" : "false") . "\n";
return $payload;
}
# gem install jwt
require "jwt"
require "base64"
def verify_player_signed(token)
secret_base64 = ENV.fetch("JWS_SECRET")
secret = Base64.decode64(secret_base64)
begin
payload, _header = JWT.decode(token, secret, true, { algorithm: "HS256" })
iat = payload["iat"]
max_age_seconds = 24 * 60 * 60
if (Time.now.to_i - iat) > max_age_seconds
raise "Token too old"
end
player = payload["player"]
raise "Missing player payload" unless player.is_a?(Hash)
puts "playerId: #{player['playerId']}"
puts "registered: #{player['registered']}"
payload
rescue JWT::DecodeError => e
warn "Invalid token: #{e.message}"
raise
end
end
# pip install PyJWT
import os
import base64
import jwt
import time
def verify_player_signed(token: str) -> dict:
secret_b64 = os.environ["JWS_SECRET"]
secret = base64.b64decode(secret_b64)
try:
payload = jwt.decode(token, secret, algorithms=["HS256"])
iat = payload["iat"]
max_age_seconds = 24 * 60 * 60
now_seconds = int(time.time())
if now_seconds - int(iat) > max_age_seconds:
raise ValueError("Token too old")
player = payload.get("player")
if not isinstance(player, dict):
raise ValueError("Missing player payload")
print("playerId:", player.get("playerId"))
print("registered:", player.get("registered"))
return payload
except jwt.InvalidTokenError as e:
print("Invalid token:", str(e))
raise
// JJWT (io.jsonwebtoken). Add jjwt-api + a runtime impl (jjwt-impl + jjwt-jackson).
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jws;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.security.Keys;
import javax.crypto.SecretKey;
import java.util.Base64;
import java.util.Date;
public class VerifyJwsHs256 {
public static Claims verifyPlayerSigned(String token) {
String secretBase64 = System.getenv("JWS_SECRET");
if (secretBase64 == null || secretBase64.isEmpty()) {
throw new IllegalStateException("Missing JWS_SECRET");
}
byte[] keyBytes = Base64.getDecoder().decode(secretBase64);
SecretKey key = Keys.hmacShaKeyFor(keyBytes);
try {
Jws<Claims> jws = Jwts.parser()
.verifyWith(key)
.build()
.parseSignedClaims(token);
Date issuedAt = jws.getPayload().getIssuedAt();
long maxAgeSeconds = 24 * 60 * 60;
long ageSeconds = (System.currentTimeMillis() - issuedAt.getTime()) / 1000;
if (ageSeconds > maxAgeSeconds) {
throw new IllegalArgumentException("Token too old");
}
Claims player = jws.getPayload().get("player", Claims.class);
if (player == null) {
throw new IllegalArgumentException("Missing player payload");
}
String playerId = player.get("playerId", String.class);
Boolean registered = player.get("registered", Boolean.class);
System.out.println("playerId: " + playerId);
System.out.println("registered: " + registered);
return jws.getPayload();
} catch (Exception e) {
System.out.println("Invalid token: " + e.getMessage());
throw e;
}
}
}
// NuGet: System.IdentityModel.Tokens.Jwt
using System;
using System.IdentityModel.Tokens.Jwt;
using Microsoft.IdentityModel.Tokens;
class VerifyJwsHs256
{
static JwtPayload VerifyPlayerSigned(string token)
{
var secret = Environment.GetEnvironmentVariable("JWS_SECRET");
if (string.IsNullOrWhiteSpace(secret))
{
throw new InvalidOperationException("Missing JWS_SECRET");
}
var key = new SymmetricSecurityKey(Convert.FromBase64String(secret));
var handler = new JwtSecurityTokenHandler();
var parameters = new TokenValidationParameters
{
ValidateIssuerSigningKey = true,
IssuerSigningKey = key,
// set these to true + configure ValidIssuer/ValidAudience in real apps
ValidateIssuer = false,
ValidateAudience = false,
RequireExpirationTime = false,
ValidateLifetime = false,
ClockSkew = TimeSpan.FromMinutes(1),
// IMPORTANT: lock the allowed algs
ValidAlgorithms = new[] { SecurityAlgorithms.HmacSha256 }
};
try
{
var principal = handler.ValidateToken(token, parameters, out var validatedToken);
var jwt = (JwtSecurityToken)validatedToken;
var iat = Convert.ToInt64(jwt.Payload["iat"]);
var maxAgeSeconds = 24 * 60 * 60;
var ageSeconds = DateTimeOffset.UtcNow.ToUnixTimeSeconds() - iat;
if (ageSeconds > maxAgeSeconds)
{
throw new InvalidOperationException("Token too old");
}
var player = jwt.Payload["player"] as IDictionary<string, object>;
if (player is null)
{
throw new InvalidOperationException("Missing player payload");
}
Console.WriteLine($"playerId: {player["playerId"]}");
Console.WriteLine($"registered: {player["registered"]}");
return jwt.Payload;
}
catch (Exception e)
{
Console.WriteLine("Invalid token: " + e.Message);
throw;
}
}
}
Player data
If your game does not have a backend, Jest provides a simple key-value store for per-player data.
Player data is stored alongside the player record on the Jest platform and is persistent across sessions and devices. All values must be serializable to JSON.
Player data is written directly from the game client and must not be used to store sensitive information or data that requires strong security guarantees.
Player data is limited to 1 MB per game; if this limit is exceeded, further writes will fail until the stored data size is reduced.
JestSDK.data.getAll()
Returns a snapshot of all key-value data stored for the current player.
JestSDK.data.getAll(): { [key: string]: unknown };
Example:
const playerData = JestSDK.data.getAll();
console.log("Player data:", playerData);
// This returns a snapshot; modifying it does not update stored data.
playerData.coins = 1000; // does not affect stored player data
JestSDK.data.get(key)
Returns the value stored for the given key in the player data, or undefined if the key does not exist.
JestSDK.data.get(key: string): unknown;
Example:
const coins = JestSDK.data.get("coins");
console.log("Player coins:", coins);
JestSDK.data.set(data)
Merges the provided key-value pairs into the player data, setting multiple keys at once.
This method performs a shallow merge and does not replace existing data. To remove a key, use JestSDK.data.delete(key) or set its value to undefined.
JestSDK.data.set(data: { [key: string]: unknown }): void;
Example:
JestSDK.data.set({ coins: 500, level: 2 });
JestSDK.data.set(key, value)
Sets the value for the given key in the player data. This is a shorthand for setting a single key.
JestSDK.data.set(key: string, value: unknown): void;
Example:
JestSDK.data.set("coins", 750);
JestSDK.data.delete(key)
Deletes the given key from the player data.
JestSDK.data.delete(key: string): void;
Example:
JestSDK.data.delete("temporaryBoost");
JestSDK.data.flush()
Flushes any pending player data changes to the server. The SDK batches multiple updates to reduce network requests; calling this method ensures all pending changes are sent immediately.
JestSDK.data.flush(): Promise<void>;
Example:
// Update some data.
JestSDK.data.set("score", 1500);
// Flush pending changes before navigating away
// or when data must be persisted immediately.
await JestSDK.data.flush();
console.log("All player data changes have been saved to the server.");