Common integration tasks
Recipes for the integrations every game studio ends up building, covering IAP fulfilment, daily quests, tournaments, matchmaker handoff, and migration.
These are the shapes most studios reach for in their first weeks with Kraty. Each recipe maps a product goal to the smallest set of Kraty calls that gets you there, with the gotchas called out.
In-app purchase fulfilment
You verified a Google Play, App Store, or Stripe receipt on your
backend and need to credit the player. Use the
server SDK, never the game client. A leaked
client_sdk key cannot mint currency, but a leaked
server_integration key can.
Use the receipt id as the idempotency key so retries (network glitches, store webhooks firing twice, your own queue replays) can 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 you get:
- Atomic, audited credit + grant.
- Replay-safe: calling the same key twice is a no-op.
- A webhook (
wallet.changedandinventory.changed) you can reflect into your own analytics.
Need the full surface? See Server SDK → Wallet credit and debit and Inventory grant and revoke.
Daily quest
A daily challenge with a fixed reward. The same shape covers any single-completion challenge.
Configure the event in the portal
Create an event with availability.mode: 'recurring_windows'
(daily reset), leaderboard.mode: 'global' or 'none', and a
fixed_bundle reward policy.
Start the attempt and report progress
From the game client, start an attempt when the player engages and push progress as they play.
const eventKey = 'daily_kill_10_enemies';
final start = await kraty.events.start(eventKey);
// On every kill:
await kraty.events.progress(
eventKey,
start.attempt.id,
const ProgressInput(mode: 'increment', metrics: {'kills': 1}),
);Claim the rewards when the threshold trips
Kraty fires the reward as a pending grant. The client claims it on next reach:
final pending = await kraty.grants.listPending();
for (final g in pending) {
await kraty.grants.claim(g.id);
}Quests reset on the next window automatically, so the player can re-enter the event tomorrow with no extra wiring on your side.
Cash out a score-attack run
An untimed high-score event: the player racks up points, then taps
End run when they are done. There is no completion target, so
nothing finalizes the attempt on its own; events.finish is what
ends the run and settles the score. Its outcome is completed
(target-less events always complete on finish), so completion
rewards roll immediately.
const eventKey = 'endless_survival';
final start = await kraty.events.start(eventKey);
// Player scores over the run; call as often as the game reports.
await kraty.events.progress(
eventKey, start.attempt.id,
const ProgressInput(mode: 'increment', metricValue: 1),
);
// ...more increments as the run continues...
// Player taps "End run": finalize at the current score.
final res = await kraty.events.finish(eventKey, start.attempt.id);
if (res.outcome == 'completed') {
await kraty.grants.collectAll(); // pick up the payout
}See How an attempt ends for the
completed vs expired outcome semantics.
Tournament with prize pool
A seven-day competition where the top ten 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 gems[2, 5]→ 250 gems[6, 10]→ 100 gems
That is the whole setup. Players post scores through the SDK as usual; when the window closes, Kraty rolls grants for the top ten and webhooks notify your backend. The player picks up their grant on next login.
For a 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 and scoring window.
The event's leaderboard.mode must 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',
externalPlayerIds: ['alice', 'bob', 'carol'],
capacity: 4,
fillBots: true,
});The key is your studio's own idempotency token: POSTing twice
with the same key returns the existing lobby. Player 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, Lootlocker, or an in-house backend. The
migrate endpoints
take up to 1,000 rows per call and surface per-row failures
so a single bad row does not take out the batch.
// Each row's idempotencyKey is typically your stable id for that
// player, wallet entry, or 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 and inventory follow the same shape.
Webhooks are not emitted during migration so a 100k-player import does not flood your backend. You get a clean cutover and run any onboarding side-effects yourself once the import is done.
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.
Verify the player
Your support or account-settings UI receives the request and verifies the user's identity through your own auth.
Tell Kraty to erase
const outcome = await kraty.players.delete('alice', {
reason: 'gdpr_erasure',
});
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;
}Mirror the deletion in your own systems
The player.deleted webhook carries the original external id one
last time, so use it to remove or anonymize the player in your CRM,
analytics, and BI pipelines.
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:
const bundle = await kraty.players.export('alice');
// bundle.player, bundle.attempts, bundle.grants, bundle.inventory,
// bundle.wallet, bundle.lobbies: everything 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 and 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 if you have not run through it yet.
Authentication
Which key belongs where.
Client SDKs
Language-specific guides: TypeScript, Unity, Flutter.
Server SDKs
For backend-side fulfilment: Node and Python.
Webhooks
Listening for what Kraty pushes back.
Error reference
Every error code, when it fires, and how to handle it.