Skip to main content
For user-facing chat UIs, run the Chat XDK in the browser via its JavaScript/WASM package (@xdevplatform/chat-xdk). Private identity and signing keys stay on the user’s device. Your servers (and X) only ever see ciphertext, public keys, and OAuth tokens—not the PIN or private key material used to encrypt and sign messages. This page is the recommended architecture for client apps. For PIN and key-handling rules that apply to every app type, see Handling private keys.

Why WASM for UI apps

End users should never paste their encryption PIN into your backend, and your backend should never hold their identity private keys. If a third-party server receives a user’s PIN or root private keys, that party can decrypt conversation keys wrapped to that identity even after the user revokes OAuth access. Client-side WASM avoids that class of failure for legitimate apps.
Sharing a PIN or private key with a third party is like sharing a password for encrypted DMs. OAuth disconnect does not revoke keys the app already obtained. Prefer WASM so keys never leave the browser; document risks clearly when you cannot. See Handling private keys.

Split crypto (browser) from API transport (your backend or direct X API with a user token): A common pattern (used by internal demos such as browser chat clients) is: WASM + React (or similar) in the frontend, TypeScript XDK in Next.js (or other) API routes so the browser never talks to api.x.com with a long-lived secret if you prefer not to. Crypto still runs only in the browser.

Install the browser package

The compiled WASM engine ships inside @xdevplatform/chat-xdk; there is no separate Rust toolchain for consumers. Requires a modern browser (and Node.js 18+ if you share code with SSR—run crypto only on the client).

Session flow (PIN once, keys stay in memory)

Do not prompt for the PIN on every message. Unlock once per browser session, keep the Chat instance in memory (module singleton, React context, etc.), then encrypt and decrypt against that unlocked instance.

UX expectations

Avoid re-prompting the PIN on every action. Demo apps sometimes call unlock repeatedly for simplicity. Production UIs should unlock once, hold the instance in memory, and only ask for the PIN again after reload, logout, or lock().

What your server is allowed to see

Realm tokens for Juicebox are not the user’s PIN. They authorize the backup protocol for that user and key version. Keep minting them on a backend that already holds the user OAuth context.

Secure key backup in the browser

Client apps should use secure key backup (setup / unlock with a passcode), not a raw key file:
  1. Load juicebox_config from the user’s public-key record (public_key.fields=juicebox_config).
  2. createChat({ juiceboxConfig, getAuthToken }).
  3. First time: generateKeypairs → register public keys with X → setup(pin).
  4. Later: unlock(pin) on this device (or a new device with the same PIN).
The Chat XDK’s browser path recovers keys into WASM and does not expose raw private-key export on the public createChat surface, so application JavaScript is not encouraged to pull root key bytes into the page. Prefer that model over hand-rolled localStorage key dumps. Full registration and unlock steps: Getting Started. Concepts: Cryptography primer.

Browser hardening checklist

  • Run Chat XDK only in client bundles (no SSR of unlocked keys).
  • Treat the unlocked Chat instance like a live session secret: do not put it on window, do not log it, do not post it to analytics.
  • Defend against XSS: CSP, careful dangerouslySetInnerHTML / markdown rendering, dependency hygiene. XSS in a chat app can reach keys in memory even when keys never hit the network.
  • Use HTTPS everywhere; never mix crypto pages with insecure scripts.
  • Prefer minimal OAuth scopes; request DM scopes only when needed and explain them in your product UI.
  • On logout, call lock() / free() and discard the instance.
Storage recommendations (what not to put in localStorage, how to think about session persistence) are in Handling private keys.

Next steps

  1. Handling private keys — PIN warnings, storage, bots vs UI apps
  2. Getting Started — full key registration and first message
  3. Chat XDK — API reference for createChat, encrypt, decrypt
  4. Real-time events — deliver ciphertext to the client for local decrypt