Setup & verification

Your backend is the relying party: the wallet identifies the player, and your server is the thing that decides to trust that identity and act on it. Everything money-moving — the ledger, the treasury, referral payouts — runs through one backend client authenticated with your secret key. This page installs it, constructs it, and covers its most important job: turning an untrusted session token from the browser into a canonical, verified identity.

Install

bash
npm install @iamgame/wallet-sdk-server

Construct the client

One IAMGameWalletServer per process. It takes your secretKey and a baseUrl that includes the version path. The constructor throws if the key doesn’t start with sk_, so a publishable key can never accidentally reach the server surface.

TS
import { IAMGameWalletServer } from "@iamgame/wallet-sdk-server";

// Construct ONCE at boot and reuse. sk_ is a backend secret — never the browser.
const wallet = new IAMGameWalletServer({
  secretKey: process.env.IAMGAME_WALLET_SECRET_KEY!, // sk_live_… or sk_test_…
  baseUrl: "https://api-wallet.iamgame.com/v1",       // include the /v1 version path
});
Never ship the secret key
An sk_ can move money for any user in your app — verify sessions, run the ledger, pay referrals. Keep it in your secret manager and out of the browser bundle. If it leaks, rotate it in the developer portal.

Why you verify server-side

The client never gets to tell your backend who it is. A wallet session token is a bearer credential the wallet issued; anyone holding it can present it, and a malicious client can claim any userId it likes in a request body. verifySession is the only thing that turns that token into a trusted userId + wallet address, cryptographically checked and scoped to your key’s app and environment. Verify first, then trust only what it returns.

The server-to-server handshake

StepWhoWhat happens
1ClientPlayer signs into the wallet (Telegram / browser wallet / email); the SDK returns a session accessToken.
2Client → your backendThe client POSTs that accessToken to your login endpoint. Nothing else is trusted yet.
3Your backendCall wallet.verifySession(token) with your sk_. The wallet confirms it and returns the canonical identity.
4Your backendUpsert your own user against id.userId and mint your own app JWT. From here your endpoints trust your token.

Verify a session

Backend SDK (recommended):

TS
import { IAMGameWalletServer } from "@iamgame/wallet-sdk-server";

const wallet = new IAMGameWalletServer({
  secretKey: process.env.IAMGAME_WALLET_SECRET_KEY!,
  baseUrl: "https://api-wallet.iamgame.com/v1",
});

// Your login handler. The browser signed into the wallet and POSTed you its
// accessToken — treat it as an untrusted claim until this call returns.
export async function login(req, res) {
  const { sessionToken } = req.body; // the wallet accessToken from your client

  // Server-to-server introspection with your sk_. Throws if the token is
  // invalid/expired, or scoped to a different app or environment than your key.
  const id = await wallet.verifySession(sessionToken);
  // id: {
  //   userId, appId, environment, authMethod,
  //   walletAddress,                      // active wallet, or null
  //   wallets:    [{ address, environment, custody, status }],
  //   identities: [{ id, type, externalId, profile }],
  // }

  // Upsert YOUR own user keyed to the canonical wallet identity, then mint YOUR
  // own app JWT. Never trust a userId the browser claims — trust id.userId.
  const user = await db.users.upsert({
    walletUserId: id.userId,
    address: id.walletAddress,
  });
  res.json({ token: signYourAppJwt({ sub: user.id }) });
}

Or call the endpoint directly:

bash
curl -X POST https://api-wallet.iamgame.com/v1/sessions/verify \
  -H "Authorization: Bearer $IAMGAME_WALLET_SECRET_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "sessionToken": "<player wallet session token>" }'

POST /v1/sessions/verify · auth Authorization: Bearer sk_… · body { sessionToken }. Secret-key only — it must never be reachable from the browser.

What you get back

The canonical, verified identity for the player — your join key and everything you need to act on it:

json
{
  "userId": "c8d85dc9-…",
  "appId": "5a00769e-…",
  "environment": "test",
  "authMethod": "telegram",
  "walletAddress": "J8gpeci3qKpYC34pw1Sk4XLncMawipDXGtLtVfoEq7mq",
  "wallets": [
    { "address": "J8gp…", "environment": "test", "custody": "self", "status": "active" }
  ],
  "identities": [
    {
      "id": "…",
      "type": "telegram",
      "externalId": "6931997952",
      "profile": { "username": "karpi", "firstName": "Piyush" }
    }
  ]
}
  • userId — the wallet’s stable user id. This is your join key; store it against your own user row.
  • walletAddress / wallets[] — the player’s managed Solana address(es), so you can confirm an on-chain address really belongs to this user.
  • identities[] — each login identity as a (type, externalId) pair with its captured profile (e.g. the Telegram username) for display.
  • environmenttest or live, always matching your key. A test session presented with a live key is refused.

Errors — it fails closed

StatusCodeMeaning
401Bad or missing secret key.
403auth/invalid_tokenThe session isn’t in your key’s scope, or its environment doesn’t match your key.
404user/not_foundThe session's user no longer exists.

Scope & isolation

Verification is bound to your key’s scope. Apps in the shared (default) scope resolve the same portable user across the ecosystem — the same player has one wallet everywhere, which is exactly the cross-app login handshake. An isolated app (e.g. a regulated entity) has app-private users: only that app’s key can verify its sessions. Choose isolation at app creation when a tenant’s users must never be resolvable from anywhere else.

Where to go next

  • Authentication — the client side: login methods and how the session token is issued.
  • Choose a money model — ledger vs treasury vs pool escrow, the decision that shapes your backend.