# Guide: Authenticated chat with your users' wallets Tie chat identity to your users by minting short-lived tokens on your backend and having users sign a wallet challenge. This is the `app-trusted+wallet` mode, the default for production embeds. **You'll need:** an embed in `app-trusted+wallet` mode, its `appId` and **app secret** (open your project in [the dashboard](https://portal.cherry.fun/dashboard) → Chat embeds and API keys), and a backend you control. ## 1. Mint tokens on your backend Sign a JWT with your app secret. The user's wallet is `sub`, your app is `app_id`. Keep the secret server-side. ```ts // POST /api/cherry-embed-token import jwt from 'jsonwebtoken'; import { randomUUID } from 'node:crypto'; export function mintCherryToken(walletAddress: string) { return jwt.sign( { sub: walletAddress, app_id: process.env.CHERRY_APP_ID }, process.env.CHERRY_APP_SECRET, { algorithm: 'HS256', expiresIn: '5m', jwtid: randomUUID() }, ); } ``` > **Note:** A full runnable example is in the public [`chat-embed-sdk`](https://github.com/cherrydotfun/chat-embed-sdk/tree/main/example/app-trusted+wallet) repo, under `example/app-trusted+wallet` (its `server.js`). ## 2. Connect the wallet and mount Register `signChallengeHandler` before `mount()`: the iframe calls it to verify wallet ownership. ```ts import { CherryEmbed } from '@cherrydotfun/chat-embed-sdk'; const provider = window.phantom?.solana; const { publicKey } = await provider.connect(); const walletAddress = publicKey.toString(); const { token } = await fetch('/api/cherry-embed-token', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ walletAddress }), }).then((r) => r.json()); const chat = new CherryEmbed({ appId: 'YOUR_EMBED_ID', container: '#cherry-chat', roomId: 'YOUR_ROOM_ID', token, walletAddress, signChallengeHandler: async (message) => { // message: Uint8Array. Sign the bytes as-is, don't re-encode them const { signature } = await provider.signMessage(message, 'utf8'); return signature; // Uint8Array }, }); await chat.mount(); ``` ## 3. Session renewal is automatic The embed token expires in ~5 minutes, so mint it fresh right before `mount()`; don't cache it. After the initial exchange the iframe renews its session silently via a rotating refresh token; there's nothing to refresh on a timer. To force a fresh exchange (e.g. after an account switch), mint a new token and call `chat.setToken(token)`. ## Where next - Create rooms from your backend → [A room per game / match / order](https://portal.cherry.fun/docs/guides/room-per-entity.md). - Full auth detail → [Embed › Authentication](https://portal.cherry.fun/docs/embed/authentication.md).