Skip to main content
Two independent things authenticate in an embedded wallet integration. Handle the wallet first: authenticate it and read its address before you create the user and mint a session in the Rain API, because creating the user’s application requires the wallet address.
  1. Wallet authentication (user → wallet provider): the user proves control of their wallet directly to the provider (Turnkey, Portal, or Privy). This happens outside Rain, through the vendor’s own SDK, and produces the authenticated material you hand to the Rain SDK. It keeps the wallet non-custodial: Rain never sees key material.
  2. Rain session (app → Rain API), optional: if your app needs to call user-scoped Rain endpoints directly instead of proxying through your backend, a short-lived Client Session Token (CST) authenticates it. The SDK’s built-in Rain API client mints and caches CSTs for you, or your backend can mint one and forward it. Otherwise, keep Rain calls on your backend with your API key. See Backend API key vs. Client Session Token.
The full flow looks like this:
📱Mobile App
🔑Wallet Provider
🖥️Your Backend
🌧️Rain API
1App → ProviderAuthenticate (passkey, OTP, or OAuth)
2Provider → AppAuthenticated session + wallet address
3App → BackendSend wallet address
4Backend → Rain APICreate user + mint session (Api-Key)
5Rain API → BackendClient Session Token (CST)
6Backend → AppForward CST
7App → Rain APIBearer cst_…
Authentication happens entirely outside Rain, through the provider’s own SDK. Your wallet provider secures the key material — for example, in a secure enclave (TEE) or as an encrypted key share on the user’s device. Rain never sees the key material and stores only wallet addresses.
The steps above, in text:
The two headers are different: your primary API key goes in the Api-Key header (backend only); a CST is a cst_-prefixed bearer token the app sends as Authorization: Bearer <cst>. The Client SDK sets the bearer header for you. For what the app can call with a CST, see Common flows.

Wallet providers

Provider authentication happens in your app, through the vendor’s own SDK. Each Rain adapter receives the authenticated material and resolves it into a wallet client. The Turnkey and Privy adapters probe the wallet when you resolve the client, so an unusable session fails fast at rain.provider(...). Portal validates its session lazily, on the first operation. Here’s how they compare:

Turnkey (managed program)

Under the managed Turnkey program, Rain provisions and configures Turnkey for you and gives you an organizationId and authProxyConfigId. Authenticate with Turnkey’s SDK, using a passkey (recommended: seamless and phishing-resistant) or an email or SMS one-time passcode. Then hand the authenticated TurnkeyContext to the adapter. The snippets below call Turnkey’s own SDK, not Rain’s:
Then register the adapter when you build the SDK: TurnkeyProvider(TurnkeyConfig(turnkey: turnkeyContext)). Omit walletAddress to use the first Ethereum account from the Turnkey context, or pass an explicit address to override it. Don’t mutate the context (re-auth, logout, wallet switch) while Rain calls are in flight. Rebuild the SDK after such changes. If your app pins bcprov-jdk18on, align it with bcprov-jdk15to18: on Android, the core module forces that version to resolve a conflict between Turnkey’s and web3j’s crypto dependencies.

Portal

Supply a valid Portal session token; the adapter constructs and initializes Portal itself from the token and the SDK’s RPC endpoints (mapped to CAIP-2 eip155:<chainId> form). On iOS, backup storage (iCloud, Keychain, password) is configured for you.
Portal doesn’t validate the session at resolve time: on iOS an empty token throws RainSDKError.unauthorized at resolution, and an invalid or expired token surfaces as RAIN_501 on the first operation. On Android, chainId optionally sets the default chain (falls back to RainChain.AVALANCHE_MAINNET when configured, else the first configured chain). To reach Portal-only features (backup, recovery) on iOS without importing PortalSwift into your Rain code, use the creation hook:

Privy

Your app owns Privy authentication end to end. Initialize the Privy SDK (on Android, in Application.onCreate()), log the user in (SMS, email, OAuth, or passkey), and make sure an embedded Ethereum wallet exists. Then hand the authenticated Privy singleton to the adapter.
Omit walletAddress to use the user’s first embedded Ethereum wallet. Signing and broadcasting route through Privy’s EIP-1193 embedded-wallet provider; balance and fee reads go through the RPC endpoints you configured on the builder, not through Privy.
  • If no user is logged in, the adapter throws RAIN_201.
  • If there’s no embedded wallet, it throws RAIN_404.
  • getTransactions returns an empty result, since Privy has no history endpoint.
Continue with Set up a wallet to build the SDK, resolve the wallet, and read its address.

Built-in Rain API client

Independently of wallet auth, RainSdk includes a client for the Rain issuing API. Configure it once and the SDK fetches the user’s collateral contracts and admin withdrawal signatures itself, minting and caching the required short-lived CST automatically. These calls never resolve or authenticate a wallet provider. Configure it at build time, or later on the built instance:
Three environments are available:
The SDK exchanges the apiKey you pass for a short-lived CST on-device. Never embed your primary Api-Key in a production binary: deliver a key scoped to this use from your backend at runtime, or keep signature fetching server-side and pass the result to withdrawCollateral yourself. Credentials are held in memory only and never persisted. See Client side considerations.

Session handling

The SDK exchanges your Api-Key for a CST (POST /v1/issuing/users/{userId}/sessions) and sends it as Authorization: Bearer <cst> on data calls.
  • The token is cached and reused while valid, and re-minted within 60 seconds of expiry.
  • A 401/403 invalidates the cached token and retries the mint exactly once before surfacing RAIN_202.
  • Calling any built-in API method without configuring credentials throws RAIN_104.
  • Non-auth HTTP failures surface as RAIN_302 with the status code.

Mint a Rain session token (backend)

If you’d rather not put any API key in the app, your backend can mint the CST instead and forward it. The user must exist in Rain before you can mint a session, so create the user’s application first (backend, with the wallet address; see Common flows). Then exchange your primary Api-Key for a user-scoped Client Session Token and return the token to your app.
The token is scoped to userId and expires after roughly an hour. Your app sends it as Authorization: Bearer <cst>. When it expires, mint a new one: the SDK surfaces an expired provider session as RAIN_201 and an invalid API key or session as RAIN_202. Use the sandbox host https://api-dev.rain.xyz/v1 while you build, and https://api.rain.xyz/v1 for production. CST issuance is enabled per tenant: contact platform@rain.xyz if a 403 says it isn’t enabled for your account.

What’s next

Set Up a Wallet

Build the SDK and create the user’s wallet.

Common flows

See the full backend-vs-app integration path.