Skip to content
Indiana
Dashboard
Docs
Browse docs / Apps, environments & identity

Apps, environments & identity

Understand Customer identity, credentials, scope, sessions and account changes.

API v1Updated

Fix scope and identity first

  • Sandbox and Live hold separate Customers and business data for each App. Keys and Customer credentials stay bound to one scope; selecting UI or sending headers cannot switch authorization.
  • Your backend verifies App login, then identifies a Customer by stable externalUserId. Console members and Customers are different identities; a UUID or email is not authentication.

Related:HTTP quickstart·Keys and permissions

Sessions, Guests and account changes

  • Your backend issues a short-lived Customer Token only for the signed-in user. The browser reads its own data without an arbitrary customerId.
  • Keep tokens in memory. Refresh through your authenticated backend after expiry. On sign-out or account changes, destroy old UI, cancel requests and clear previous data.
  • Guest login to an existing Customer switches identity without merging holdings or history. An existing Funnel retains its original Customer; use a new session and run after switching.

Related:Session and account-change recipe

Optional stable deduplication signal

fingerprint is an optional stable deduplication signal supplied by your host, such as a browser installation identifier or a host-collected device signal. It is separate from login identity. A Product with unique_fingerprint also deduplicates trials across Customers sharing that signal; without one, trials still deduplicate by Customer. The SDK does not collect or generate it.

  • Use the same signal from the first Guest creation onward. Retain it through login, account changes and session renewal within the same installation.
  • A Customer records the first supplied signal; later values do not replace it. In-place Guest identification retains it. Switching to an existing Customer preserves each Customer's own record.
  • A business user ID differs per account and cannot provide cross-account deduplication. Customer Tokens, Guest Tokens, session tokens and request IDs have separate lifecycles and should not be used as fingerprints.

Web: retain an installation signal on the host

This HTTPS host example retains a random installation signal in a separate HttpOnly cookie. Call POST /api/installation before Guest/Customer initialization, and share that initialization request within the page. The cookie identifies browser storage, not attested hardware. Clearing cookies, changing browsers or expiration can produce a new value; signing out does not require resetting it.

After host authentication, pass readInstallationSignal(request) and the verified user ID separately to the backend function below. Keep the Secret Key on the backend; the browser cannot choose the signed-in user. Guest creation can use the same fingerprint, while its recoverable Guest Token is retained separately by the host and reused on creation retries.

server/installation.tsTypeScript
import { randomUUID } from "node:crypto"; const cookieName = "__Host-indiana-install";const uuid = /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/; export function readInstallationSignal(request: Request): string | undefined {  const value = request.headers.get("Cookie")?.split(";")    .map((item) => item.trim())    .find((item) => item.startsWith(cookieName + "="))    ?.slice(cookieName.length + 1);  return value && uuid.test(value) ? value : undefined;} // Mount on your HTTPS host as POST /api/installation.// Initialize once before the first Guest or signed-in Customer request.export function initializeInstallation(request: Request): Response {  const headers = new Headers({ "Cache-Control": "no-store" });  if (!readInstallationSignal(request)) {    headers.set("Set-Cookie",      cookieName + "=" + randomUUID() +      "; Path=/; Secure; HttpOnly; SameSite=Lax; Max-Age=31536000");  }  return new Response(null, { status: 204, headers });}
server/customer.tsTypeScript
// Runs only on the trusted backend. The host verifies the user before calling.export async function customerForVerifiedUser(  verifiedUserId: string,  installationSignal?: string,) {  const response = await fetch(new URL("/api/v1/customers", process.env.INDIANA_BASE_URL!), {    method: "POST",    cache: "no-store",    redirect: "error",    headers: {      "Content-Type": "application/json",      Authorization: "Bearer " + process.env.INDIANA_SECRET_KEY!,      "X-Indiana-Project": process.env.INDIANA_PROJECT_ID!,      "X-Indiana-Environment": "sandbox",    },    body: JSON.stringify({      externalUserId: verifiedUserId,      ...(installationSignal === undefined ? {} : { fingerprint: installationSignal }),    }),  });  if (!response.ok) {    // Capture only these fields; never log the request body or credentials.    throw Object.assign(new Error("Indiana Customer request failed"), {      status: response.status,      requestId: response.headers.get("X-Request-Id"),    });  }  return response.json();}

Native: define installation storage and reset behavior

A native host can generate a random installation signal, persist it before use, and pass it as fingerprint through its backend. Reuse the stored value across launches, Guest sessions, login and account changes; do not regenerate it per request or session renewal. The host defines storage and behavior after reinstall, backup restoration or user reset. Do not assume reinstall always produces a new signal on every platform. Device attestation, if required, remains the host's responsibility.

Expected result

A second Customer with the same signal cannot obtain another trial of the same Product when signal uniqueness is enabled. A default paid trial falls through to a free default in its group, when present; an explicit start returns a conflict. Retain X-Request-Id for troubleshooting. Client messages do not expose another Customer, the signal or related records.