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
npm install @iamgame/wallet-sdk-serverConstruct 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.
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
});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
| Step | Who | What happens |
|---|---|---|
| 1 | Client | Player signs into the wallet (Telegram / browser wallet / email); the SDK returns a session accessToken. |
| 2 | Client → your backend | The client POSTs that accessToken to your login endpoint. Nothing else is trusted yet. |
| 3 | Your backend | Call wallet.verifySession(token) with your sk_. The wallet confirms it and returns the canonical identity. |
| 4 | Your backend | Upsert 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):
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:
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:
{
"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 capturedprofile(e.g. the Telegramusername) for display.environment—testorlive, always matching your key. A test session presented with a live key is refused.
Errors — it fails closed
| Status | Code | Meaning |
|---|---|---|
401 | — | Bad or missing secret key. |
403 | auth/invalid_token | The session isn’t in your key’s scope, or its environment doesn’t match your key. |
404 | user/not_found | The 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.