> ## Documentation Index
> Fetch the complete documentation index at: https://docs.whop.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Authentication

> Authenticate users for embedded chat with company-scoped tokens or OAuth

export const Auth = ({value, children}) => {
  const values = Array.isArray(value) ? value : [value];
  return <div className="pp-content-auth" data-auth-modes={values.join(" ")}>
			{children}
		</div>;
};

export const Platform = ({value, children}) => {
  const values = Array.isArray(value) ? value : [value];
  return <div className="pp-content" data-platforms={values.join(" ")}>
			{children}
		</div>;
};

export const PlatformSelect = ({children}) => {
  const platforms = [{
    value: "react",
    label: "React"
  }, {
    value: "vanillajs",
    label: "Vanilla JS"
  }, {
    value: "swift",
    label: "Swift"
  }];
  const authMethods = [{
    value: "token",
    label: "Token"
  }, {
    value: "oauth",
    label: "OAuth"
  }];
  const [selected, setSelected] = React.useState("react");
  const [selectedAuth, setSelectedAuth] = React.useState("token");
  const wrapperRef = React.useRef(null);
  React.useEffect(() => {
    const wrapper = wrapperRef.current;
    if (!wrapper) return;
    const updateToc = () => {
      const hiddenIds = new Set();
      const visibleIds = new Set();
      wrapper.querySelectorAll(".pp-content").forEach(el => {
        const plats = (el.getAttribute("data-platforms") || "").split(" ");
        const isVisible = plats.includes(selected);
        el.querySelectorAll("[id]").forEach(child => {
          if (isVisible) {
            visibleIds.add(child.id);
          } else {
            hiddenIds.add(child.id);
          }
        });
      });
      wrapper.querySelectorAll(".pp-content-auth").forEach(el => {
        const modes = (el.getAttribute("data-auth-modes") || "").split(" ");
        const isVisible = modes.includes(selectedAuth);
        el.querySelectorAll("[id]").forEach(child => {
          if (isVisible) {
            visibleIds.add(child.id);
          } else {
            hiddenIds.add(child.id);
          }
        });
      });
      document.querySelectorAll('a[href^="#"]').forEach(a => {
        const id = decodeURIComponent(a.getAttribute("href").slice(1));
        const li = a.closest("li");
        if (!li) return;
        if (hiddenIds.has(id) && !visibleIds.has(id)) {
          li.style.display = "none";
        } else if (visibleIds.has(id) || hiddenIds.has(id)) {
          li.style.display = "";
        }
      });
    };
    updateToc();
    const timer = setTimeout(updateToc, 200);
    return () => clearTimeout(timer);
  }, [selected, selectedAuth]);
  return <div className="pp-wrapper" ref={wrapperRef} data-selected-platform={selected} data-selected-auth={selectedAuth}>
			<div className="pp-bar">
				<div className="pp-group">
					<span className="pp-label">Auth</span>
					<div className="pp-buttons">
						{authMethods.map(p => <button key={p.value} onClick={() => setSelectedAuth(p.value)} className={`pp-button ${selectedAuth === p.value ? "pp-button-active" : ""}`} aria-pressed={selectedAuth === p.value}>
								{p.label}
							</button>)}
					</div>
					<select className="pp-select" value={selectedAuth} onChange={e => setSelectedAuth(e.target.value)}>
						{authMethods.map(p => <option key={p.value} value={p.value}>
								{p.label}
							</option>)}
					</select>
				</div>
				<div className="pp-group">
					<span className="pp-label">Platform</span>
					<div className="pp-buttons">
						{platforms.map(p => <button key={p.value} onClick={() => setSelected(p.value)} className={`pp-button ${selected === p.value ? "pp-button-active" : ""}`} aria-pressed={selected === p.value}>
								{p.label}
							</button>)}
					</div>
					<select className="pp-select" value={selected} onChange={e => setSelected(e.target.value)}>
						{platforms.map(p => <option key={p.value} value={p.value}>
								{p.label}
							</option>)}
					</select>
				</div>
			</div>
			{children}
		</div>;
};

<PlatformSelect>
  Before using the chat SDK, you need to authenticate users. Pick one of two approaches:

  | Approach                       | Best for                                                                                            |
  | ------------------------------ | --------------------------------------------------------------------------------------------------- |
  | **Company-scoped user tokens** | Apps that already have their own users. Your server mints Whop tokens for them — no sign-in needed. |
  | **OAuth**                      | Apps that want users to sign in to Whop themselves via a sign-in page or webview.                   |

  <Auth value="token">
    ## Company-scoped user tokens

    Use this approach if **your app already has its own users** (via your own auth / session system). You keep your existing login flow and mint a Whop token for whichever user is currently signed in — no extra sign-in step.

    Your server exchanges your Whop API key for a short-lived user token on demand and returns it to the client. You decide which user the token is for.

    ### 1. Gather your credentials

    You'll need three things:

    1. **API key** — create one at the [Whop Developer Dashboard](https://whop.com/dashboard/developer). Treat it like a password and keep it on your server only.
    2. **Company ID** — find it in your dashboard URL: `whop.com/dashboard/biz_XXXXXXXXX/`.
    3. **User ID** — the user you want to authenticate. In production, derive this from your own auth / session system.

    ### 2. Create a token endpoint on your server

    Add an endpoint that calls `POST https://api.whop.com/api/v1/access_tokens` with your API key and returns the resulting token to the client.

    <Tabs>
      <Tab title="Next.js">
        ```typescript app/api/chat/token/route.ts theme={null}
        import { NextResponse } from "next/server";
        import Whop from "@whop/sdk";

        const client = new Whop({ apiKey: process.env.WHOP_API_KEY });

        export async function POST() {
          // Derive these from your own auth / session in production.
          const user_id = "user_XXXXXXXXXXXX";
          const company_id = "biz_XXXXXXXXXXXXX";

          const { token } = await client.accessTokens.create({
            company_id,
            user_id,
            scoped_actions: [
              "chat:message:create",
              "chat:read",
              "dms:read",
              "dms:message:manage",
              "dms:channel:manage",
              "support_chat:read",
              "support_chat:message:create",
            ],
          });

          return NextResponse.json({ token });
        }
        ```
      </Tab>

      <Tab title="Express">
        ```typescript server.ts theme={null}
        import express from "express";
        import Whop from "@whop/sdk";

        const app = express();
        app.use(express.json());

        const client = new Whop({ apiKey: process.env.WHOP_API_KEY });

        app.post("/api/chat/token", async (_req, res) => {
          // Derive these from your own auth / session in production.
          const user_id = "user_XXXXXXXXXXXX";
          const company_id = "biz_XXXXXXXXXXXXX";

          const { token } = await client.accessTokens.create({
            company_id,
            user_id,
            scoped_actions: [
              "chat:message:create",
              "chat:read",
              "dms:read",
              "dms:message:manage",
              "dms:channel:manage",
              "support_chat:read",
              "support_chat:message:create",
            ],
          });

          res.json({ token });
        });

        app.listen(3000);
        ```
      </Tab>

      <Tab title="cURL">
        ```bash theme={null}
        curl -X POST https://api.whop.com/api/v1/access_tokens \
          -H "Authorization: Bearer $WHOP_API_KEY" \
          -H "Content-Type: application/json" \
          -d '{
            "company_id": "biz_XXXXXXXXXXXXX",
            "user_id": "user_XXXXXXXXXXXX",
            "scoped_actions": [
              "chat:message:create",
              "chat:read",
              "dms:read",
              "dms:message:manage",
              "dms:channel:manage",
              "support_chat:read",
              "support_chat:message:create"
            ]
          }'
        ```
      </Tab>
    </Tabs>

    <Platform value="react">
      ### 3. Fetch the token from your client

      Provide a `getToken` function that fetches from your endpoint. The chat elements call this whenever they need to authenticate, and again automatically when the token expires.

      ```typescript theme={null}
      async function getToken() {
        const response = await fetch("/api/chat/token", { method: "POST" });
        const data = await response.json();
        return data.token;
      }
      ```
    </Platform>

    <Platform value="vanillajs">
      ### 3. Fetch the token from your client

      Provide a `getToken` function that fetches from your endpoint. The chat elements call this whenever they need to authenticate, and again automatically when the token expires.

      ```typescript theme={null}
      async function getToken() {
        const response = await fetch("/api/chat/token", { method: "POST" });
        const data = await response.json();
        return data.token;
      }
      ```
    </Platform>

    <Platform value="swift">
      ### 3. Implement a token provider in your app

      Create a class that conforms to `WhopTokenProvider` and point it at your server endpoint. The SDK calls `getToken()` whenever authentication is needed and again before the token expires.

      ```swift theme={null}
      import WhopElements

      class WhopAPITokenProvider: WhopTokenProvider {
          private let serverURL = URL(string: "https://your-server.com")!

          func getToken() async -> WhopTokenResponse {
              do {
                  var req = URLRequest(url: serverURL.appendingPathComponent("api/chat/token"))
                  req.httpMethod = "POST"

                  let (data, _) = try await URLSession.shared.data(for: req)
                  let json = try JSONSerialization.jsonObject(with: data) as! [String: Any]
                  return WhopTokenResponse(accessToken: json["token"] as? String ?? "")
              } catch {
                  return WhopTokenResponse(accessToken: "")
              }
          }
      }
      ```

      Pass the token provider to the SDK on app launch:

      ```swift theme={null}
      .task {
          await WhopSDK.configure(tokenProvider: WhopAPITokenProvider())
      }
      ```
    </Platform>

    ### Required scopes

    Request these scopes when creating the token:

    | Scope                                                  | Purpose          |
    | ------------------------------------------------------ | ---------------- |
    | `chat:message:create`, `chat:read`                     | Experience chats |
    | `dms:read`, `dms:message:manage`, `dms:channel:manage` | Direct messages  |
    | `support_chat:read`, `support_chat:message:create`     | Support chats    |

    ## Sync your users

    Before you can mint a token for a user, that user needs to exist on Whop. You can optionally set a company-specific name and profile picture that will be shown for that user in chat.

    ### 1. Enroll users as connected accounts

    Create a connected account for each user on your platform. This is the same flow used by the [payouts SDK](/developer/platforms/render-payout-portal) — see the [Enroll connected accounts](/developer/platforms/enroll-connected-accounts) guide for the full walkthrough.

    ```typescript theme={null}
    import Whop from "@whop/sdk";

    const client = new Whop({ apiKey: process.env.WHOP_API_KEY });

    const company = await client.companies.create({
      email: "user@example.com",
      parent_company_id: "biz_XXXXXXXXXXXXX", // your platform's company ID
      title: "Jane Doe",
      metadata: {
        internal_user_id: "user_12345", // your platform's user ID
      },
    });
    ```

    Save the resulting user ID on your side — you'll pass it when minting tokens in the previous step.

    ### 2. Set a company-specific name and profile picture

    Call the [Update user](/api-reference/users/update-user) endpoint with your company ID to set overrides shown for that user in the context of your company. Without a company ID, the fields update the user's global profile instead.

    Avatars are uploaded via the [Create file](/api-reference/files/create-file) endpoint — pass the returned file ID as the profile picture.

    Call this whenever a user's profile changes on your platform so Whop stays in sync.
  </Auth>

  <Auth value="oauth">
    ## OAuth

    With OAuth, users sign in to Whop themselves through a sign-in page (or webview on iOS). Your server exchanges the resulting authorization code for an access token.

    ### Setup

    <Tabs>
      <Tab title="API">
        Use the [Create App](/api-reference/apps/create-app) endpoint to create an OAuth app programmatically:

        ```typescript theme={null}
        import Whop from "@whop/sdk";

        const client = new Whop({ apiKey: process.env.WHOP_API_KEY });

        const app = await client.apps.create({
          name: "My App",
          redirect_uris: [
            "http://localhost:3000/api/auth/callback/whop",
            "https://myapp.com/api/auth/callback/whop"
          ],
        });

        console.log(app.id);            // App ID
        console.log(app.client_secret); // Client Secret
        ```

        Update redirect URIs later with the [Update App](/api-reference/apps/update-app) endpoint.
      </Tab>

      <Tab title="Dashboard">
        1. Register your app in the [Whop Dashboard](https://whop.com/dashboard/) > Developer to get an app ID

        <Platform value="react">
          2) Inside the app you just created, go to OAuth and add a redirect URL. Use your app's callback URL (e.g., `https://yourapp.com/oauth/callback`)
        </Platform>

        <Platform value="vanillajs">
          2. Inside the app you just created, go to OAuth and add a redirect URL. Use your app's callback URL (e.g., `https://yourapp.com/oauth/callback`)
        </Platform>

        <Platform value="swift">
          2. Inside the app you just created, go to OAuth and add a redirect URL. Use your bundle ID (e.g., `com.yourapp.bundle://oauth/callback`) or configure a custom one
        </Platform>

        3. On the same page, copy the required scopes from "View available scopes"
      </Tab>
    </Tabs>

    ### Required scopes

    Your OAuth configuration should include these scopes:

    | Scope                                                  | Purpose            |
    | ------------------------------------------------------ | ------------------ |
    | `openid`, `profile`, `email`                           | Basic profile info |
    | `chat:message:create`, `chat:read`                     | Experience chats   |
    | `dms:read`, `dms:message:manage`, `dms:channel:manage` | Direct messages    |
    | `support_chat:read`, `support_chat:message:create`     | Support chats      |

    <Card title="OAuth guide" icon="key" href="/developer/guides/oauth">
      See the full OAuth guide for setting up authentication and obtaining tokens
    </Card>

    <Platform value="react">
      ### Token endpoint

      Your server needs to provide a token endpoint that returns a valid OAuth token with the required scopes. The chat elements call this function whenever they need to authenticate.

      ```typescript theme={null}
      async function getToken() {
      	const response = await fetch("/api/token");
      	const data = await response.json();
      	return data.token;
      }
      ```

      See the [OAuth guide](/developer/guides/oauth) for implementing the server-side token exchange.
    </Platform>

    <Platform value="vanillajs">
      ### Token endpoint

      Your server needs to provide a token endpoint that returns a valid OAuth token with the required scopes. The chat elements call this function whenever they need to authenticate.

      ```typescript theme={null}
      async function getToken() {
      	const response = await fetch("/api/token");
      	const data = await response.json();
      	return data.token;
      }
      ```

      See the [OAuth guide](/developer/guides/oauth) for implementing the server-side token exchange.
    </Platform>

    <Platform value="swift">
      ### SDK-managed OAuth

      Call `configureWithOAuth` on app launch. The SDK handles the entire flow: showing a sign-in webview, obtaining tokens, and refreshing them automatically.

      ```swift theme={null}
      .task {
          await WhopSDK.configureWithOAuth(
              appId: "app_XXXXXXXXXXXXXX",
              scopes: [
                  "openid", "profile", "email",
                  "chat:message:create", "chat:read",
                  "dms:read", "dms:message:manage", "dms:channel:manage",
                  "support_chat:read", "support_chat:message:create",
              ]
          )
      }
      ```

      By default, the SDK uses your bundle identifier for the redirect URI (`com.yourapp.bundle://oauth/callback`). You can customize this:

      ```swift theme={null}
      await WhopSDK.configureWithOAuth(
          appId: "app_XXXXXXXXXXXXXX",
          redirectUri: "myapp://auth/callback",
          scopes: [
              "openid", "profile", "email",
              "chat:message:create", "chat:read",
              "dms:read", "dms:message:manage", "dms:channel:manage",
              "support_chat:read", "support_chat:message:create",
          ]
      )
      ```

      Make sure the redirect URI matches what you configured in the [Whop Dashboard](https://whop.com/dashboard/) > Developer > App > OAuth.

      When a user navigates to a chat view, the OAuth flow is triggered automatically if they're not already authenticated. You can also trigger sign-in and sign-out manually:

      ```swift theme={null}
      // Sign in
      try await WhopSDK.signIn()

      // Sign out
      WhopSDK.signOut()
      ```

      #### Tracking authentication state

      Use the `.whopAuthState` modifier to reactively track whether the user is signed in:

      ```swift theme={null}
      struct ChatView: View {
          @State private var isAuthenticated = false

          var body: some View {
              VStack {
                  if isAuthenticated {
                      Text("Signed In")
                  } else {
                      Text("Not Signed In")
                  }
              }
              .whopAuthState($isAuthenticated)
          }
      }
      ```

      #### Pre-filling tokens (optional)

      If you already have the user's Whop tokens from another source (e.g. a web OAuth flow or synced from your backend), you can pre-fill them to skip the sign-in webview on first launch. This is entirely optional and only relevant if your users have already authenticated with Whop elsewhere.

      ```swift theme={null}
      let tokens = await myBackend.getWhopTokens(for: currentUser)

      try await WhopSDK.preSignIn(
          accessToken: tokens.accessToken,
          refreshToken: tokens.refreshToken
      )
      ```

      The SDK extracts the token expiration from the JWT and handles refresh automatically. If the pre-filled tokens expire and can't be refreshed, the normal OAuth flow kicks in.
    </Platform>
  </Auth>
</PlatformSelect>
