Common integration tasks
Recipes for the integrations every game studio ends up building — IAP fulfilment, daily quests, tournaments, matchmaker handoff, migration.
These are the shapes most studios need on day one. Each recipe maps a real product goal to the smallest set of Kraty calls that gets you there.
In-app purchase fulfilment
You verified a Google Play / App Store / Stripe receipt on your backend and need to credit the player. Use the server SDK — never the game client.
Use the receipt id as the idempotency key so retries (network glitches, store webhooks firing twice) never double-grant.
// On your backend, after receipt verification succeeds:
await kraty.wallet.credit(externalPlayerId, 'gems', {
amount: 500,
reason: 'iap',
sourceRefId: receipt.transactionId,
idempotencyKey: receipt.transactionId,
});
await kraty.inventory.grant(externalPlayerId, 'starter_chest', {
quantity: 1,
reason: 'iap',
sourceRefId: receipt.transactionId,
idempotencyKey: receipt.transactionId,
});What it gives you:
- Atomic, audited credit + grant.
- Replay-safe — calling the same key twice is a no-op.
- A webhook (
wallet.changed,inventory.changed) you can reflect into your own analytics.
Daily quest
A daily challenge with a fixed reward — same shape works for any single-completion challenge.
- In the portal: create an event with
availability.mode: 'recurring_windows'(daily reset),leaderboard.mode: 'global'or'none', and afixed_bundlereward policy. - In the game client, start an attempt and push progress as the player plays:
await kraty.events.start(playerId, 'daily_kill_10_enemies');
// later, on every kill:
await kraty.events.progress(playerId, eventKey, attemptId, {
mode: 'increment',
metricValues: { 'kills': 1 },
});- When the threshold is hit, Kraty fires the reward as a pending grant. The client claims it:
final pending = await kraty.grants.listPending(playerId);
for (final g in pending) {
await kraty.grants.claim(playerId, g.id);
}Quests reset on the next window automatically — the player can re-enter the event tomorrow with no extra wiring on your side.
Tournament with prize pool
A 7-day competition where the top 10 players share a prize pool.
In the portal:
- Event with
availability.mode: 'exact'(a one-shot week-long window) andleaderboard.mode: 'global'. - Reward policy:
rank_scaledwith brackets like[1,1] → 1000,[2,5] → 250,[6,10] → 100.
That's the whole setup. Players post scores through the SDK as usual; when the window closes, Kraty rolls grants for the top 10 and webhooks notify your backend. The player picks up their grant on next login.
For the live UI, stream the leaderboard via SSE so the rankings repaint without polling.
Push a lobby from your own matchmaker
If you already run matchmaking (Steam, GameLift, Photon, in-house), hand Kraty the resulting roster and let it host the leaderboard + scoring window.
Requires the event's leaderboard.mode to be 'lobby_matched'.
// Your matchmaker chose the roster; tell Kraty:
const lobby = await kraty.lobbies.push('your_game_id', 'quick_brawl', {
key: 'matchmaker_match_abc123', // idempotent on YOUR id
externalPlayerIds: ['alice', 'bob', 'carol'],
capacity: 4,
fillBots: true, // pad the empty slot with a bot
});Players' game clients then start an attempt against that lobby
normally — Kraty links it up via the leaderboardId returned by
events.start.
Migrate from another platform
Bringing players, balances, and inventory over from PlayFab,
Firebase, or an in-house backend. /migrate endpoints take up to 1,000 rows per
call and surface per-row failures so a single bad row doesn't take
out the batch.
// Each row's idempotencyKey is typically your stable id for that
// player / wallet entry / inventory holding — so retries are safe.
const outcome = await kraty.migrate.players([
{ externalPlayerId: 'p_1', idempotencyKey: 'p_1' },
{ externalPlayerId: 'p_2', idempotencyKey: 'p_2', contextSnapshot: { country: 'PT' } },
]);
console.log(`${outcome.applied} created, ${outcome.skipped} replayed`);
if (outcome.failures.length) {
// Inspect outcome.failures and retry just those rows.
}Wallet + inventory follow the same shape. Webhooks are NOT emitted during migration so a 100k-player import doesn't flood your backend — you get a clean cutover.
Handle a GDPR erasure request
A player asks you to delete their data ("right to be forgotten", GDPR Article 17). Your studio is the data controller; Kraty is a processor. The flow:
- Your support / account-settings UI receives the request and verifies the user's identity.
- Your backend calls
kraty.players.delete(externalPlayerId). - Kraty anonymizes the player row + cascade (attempts, lobbies,
Redis leaderboard meta), emits one final
player.deletedwebhook with the original external id, and returns an outcome. - Your own systems (CRM, analytics, BI) remove or anonymize the player as well, using the webhook as the trigger.
const outcome = await kraty.players.delete('alice', {
reason: 'gdpr_erasure', // recorded on Kraty's audit log
});
switch (outcome.status) {
case 'erased':
// First-time deletion — the cascade ran, webhook fired.
log.info({ playerId: outcome.playerId }, 'player erased');
break;
case 'no_op_never_existed':
// GDPR-success — Kraty had no data on this player.
break;
case 'no_op_already_erased':
// Idempotent replay against the placeholder row (rare).
break;
}What survives the deletion: the financial ledger (grants, item
ledger, wallet ledger) is retained per audit requirements but
points at an anonymized player row whose external id is now a
__deleted_<uuid>__ placeholder. Nothing links those rows back to
a person.
Companion: data export (GDPR Article 15, right of access). If the player asks for a copy of their data instead of (or before) deletion, call:
const bundle = await kraty.players.export('alice');
// bundle.player, bundle.attempts, bundle.grants, bundle.inventory,
// bundle.wallet, bundle.lobbies — all the data Kraty has.
// Returns 404 if Kraty has never seen this player.The Python SDK has the same surface:
kraty.players.delete(external_player_id, reason='gdpr_erasure')
and kraty.players.export(external_player_id).
Daily login streak
Track consecutive-day logins, pay a bonus on milestones.
The cleanest shape uses two events:
- A single-metric event with daily recurrence whose
startcounts as the login. - A streak event whose
streakmetric is bumped from your game onevents.startof (1), withresetOnconfigured to zerostreakif a day was missed.
Milestone rewards fire mid-attempt the first time streak crosses
each threshold (3 days, 7 days, 30 days). See Rewards →
Milestone rewards for the wire
shape.
See also
- Quickstart — initial setup
- Authentication — which key belongs where
- SDKs — language-specific client guides
- Server SDKs — for backend-side fulfilment
- Webhooks — listening for what Kraty pushes back