Whop apps render inside an iframe onDocumentation Index
Fetch the complete documentation index at: https://docs.whop.com/llms.txt
Use this file to discover all available pages before exploring further.
whop.com. To figure out who is making a request to your app, Whop passes a short-lived JWT in the x-whop-user-token header on every same-origin request. You verify this token with the SDK to get a user ID, then check what that user is allowed to see.
Verify the iframe user token
CallverifyUserToken on each request. It reads the x-whop-user-token header, validates the JWT signature and expiry, and returns the user ID. Invalid tokens throw by default. Each SDK also has a non-throwing variant:
| Language | Non-throwing variant |
|---|---|
| TypeScript | Pass { dontThrow: true } as the second argument |
| Python | Pass dont_throw=True as a keyword argument |
| Ruby | Use verify_user_token (without the !) |
window.location.origin of your iframe. Examples:
- Relative page navigations:
<a href="/sub_page">...</a> - Fetches without a domain:
await fetch("/api/quizzes")
App.base_url domain.
My API is on a different domain
My API is on a different domain
If your frontend runs on
example.com but your API runs on api.example.com, reverse-proxy API requests through example.com/api so they carry the x-whop-user-token header.- Cloudflare: create an “origin rule” from
/apionexample.comto rewrite toapi.example.com/api. - Next.js: add a rewrite in
next.config.mjs. - nginx / Caddy: use the rewrite primitive in your server config.
This setup is required because of strict browser cross-origin cookie policies.
Local setup
Whop ships a local reverse proxy that matches the production iframe + cookie behavior, so the code you write works identically onlocalhost and in production. See the dev proxy guide for setup.
Check access
Now that you know who’s making the request, check what they’re allowed to see. Use thecheckAccess method to verify access to an Experience, Company, or Product.
Resource IDs and access levels
resource_id prefix | Checks access to |
|---|---|
biz_xxxx | Company |
prod_xxxx | Product |
exp_xxxx | Experience |
access_level is one of:
| Level | Meaning |
|---|---|
customer | User has a valid membership. For an experience, they have a membership to any product connected to it. For a product, to that specific product. For a company, to any product on it. |
admin | User is a team member of the company (any role, including moderator). |
no_access | User has no access (has_access is false). |
Customer app (Experience View)
Customer apps should gate on theexperienceId passed in path params. Return no_access or redirect if the viewer doesn’t have a valid membership.
headers() for request.headers and return a 403 Response instead of JSX.
Dashboard app (Dashboard View)
Dashboard apps should gate onaccess_level === "admin" so only company team members can load the view.
headers() for request.headers and return a 403 Response instead of JSX.
Next steps
Run a local dev proxy
Match the production iframe + cookie setup on localhost.
Request permissions
Add the scopes your app needs before publishing.
Listen to webhooks
Receive payment, membership, and entry events on your server.
Build an app view
Set up dashboard views, experiences, and discover listings.

