How authentication works
An embedded app renders inside Fanvue in an iframe. On load, Fanvue injects a short-lived session token that proves “this creator is using your app inside Fanvue right now.” Your backend trades that token for real OAuth access + refresh tokens through a delegated authorize-on-behalf flow, so you can call the Fanvue API on the creator’s behalf — including in the background, after they’ve closed the iframe. The security model is a capability split: Fanvue owns the authorize half (the creator’s identity + consent), your app owns the exchange half (your client secret + PKCE verifier). Neither side sees the other’s secrets — Fanvue hands you an authorization code but never sees your client secret, PKCE verifier, or the resulting tokens; your app never forges a creator’s identity. The recommended way to implement this is the@fanvue/builder-sdk SDK, which handles the whole exchange (PKCE, state, code exchange, refresh) so it’s hard to get wrong. If you can’t use the SDK, the full manual flow is in the appendix.
Step 1 — Register your app
In Fanvue, go to App Store → Builder → Create App:Set the app type
Choose App Type → Embedded App, and set the Embed URL to the page Fanvue should load, e.g.
https://your-app.com/embedded.Configure authentication
- Copy the Client ID.
- Reset secret and copy the Client Secret — it’s shown once.
- Add a redirect URI (e.g.
https://your-app.com/oauth/callback). It’s never visited by a browser — it only binds the authorization code — but it must match exactly what you send at token exchange. - Add the scopes your app needs (e.g.
read:self,read:creator). See Scopes.
Complete the listing
Fill in App Details (icon, tagline) and Publish (preview image, description, test credentials), then submit for review. See Publishing Your App.
Step 2 — Hosting requirements
- Serve your embed URL over HTTPS with a browser-trusted certificate.
- Send a
Content-Security-Policy: frame-ancestors https://www.fanvue.comheader so Fanvue can frame your page (usehttps://dev.fanvue.comagainst the dev environment). - Keep the client secret server-side only — never ship it to the browser.
Step 3 — Authenticate with the SDK (recommended)
When Fanvue opens your app in the iframe, it appends the session token to your embed URL (?token=...). The SDK exchanges it server-side for OAuth tokens and manages the session from there.
Add the session-exchange route
app/api/fanvue/session/route.ts
createConfig() reads your OAUTH_* and SESSION_* environment variables — set your Client ID, Client Secret, and registered redirect URI there.Authenticate in your embedded page
app/embedded/page.tsx
useEmbeddedAuth reads the session token from the URL and exchanges it on mount, well inside the token’s ~60-second lifetime. It also returns theme — see Match the creator’s theme below.Not on Next.js?
@fanvue/builder-sdk/react works with any React framework, and the core @fanvue/builder-sdk entrypoint exposes the low-level OAuth primitives (including getThemeFromUrl) for Node, Deno, or a custom server. If you can’t use the SDK at all, implement the flow yourself per the appendix.Working on the creator’s behalf in the background
To act for the creator after the iframe closes (scheduled jobs, webhooks, automation), persist the refresh token when the exchange completes:app/api/fanvue/session/route.ts
Match the creator’s theme
To feel like a native part of Fanvue, your app should render in the same light or dark theme the creator is currently using. When Fanvue opens your iframe, it appends the creator’s active colour scheme to your embed URL as?theme=light or ?theme=dark, alongside the ?token= session token.
useEmbeddedAuth reads it for you and returns theme: 'light' | 'dark' | null — null when your app is opened outside Fanvue (no ?theme= present), so always fall back to a sensible default:
app/embedded/page.tsx
Environments
| Production | Dev | |
|---|---|---|
| Platform | https://www.fanvue.com | https://dev.fanvue.com |
| Auth (Hydra) | https://auth.fanvue.com | https://auth.dev.fanvue.com |
| API | https://api.fanvue.com | https://api.dev.fanvue.com |
Gotchas / checklist
- PKCE is mandatory — an authorize-on-behalf request without a
code_challenge(S256) is rejected. The SDK handles this for you. redirect_uriat token exchange must exactly equal a registered redirect URI.consent_required(403) from authorize-on-behalf means the creator hasn’t approved the in-platform consent screen yet (or revoked it) — your app can’t create that consent; it happens in the Fanvue UI.- Request
offline_access-capable scopes at consent time if you want refresh tokens (the in-platform consent grants it so one approval covers both the live surface and offline use). - Verify and track
state, keep the client secret and PKCE verifier server-side, and never forward the session token anywhere but your own backend. - The session token has a ~60s TTL — if a user leaves the iframe open and clicks much later, re-read a fresh token (reopening the surface re-issues one).
Appendix — the manual flow (without the SDK)
Everything the SDK does, by hand. Use this if you’re not on Node/React, or you want to understand exactly what’s happening.The endpoints
| Purpose | Request |
|---|---|
| Get an authorization code (delegated) | POST {PLATFORM}/api/v1/app-integrations/authorize-on-behalf — Authorization: Bearer <session token>, body { code_challenge, code_challenge_method: "S256", state } → { code, state } |
| Exchange code for tokens | POST {HYDRA}/oauth2/token — client_secret_basic, grant_type=authorization_code, code, redirect_uri, code_verifier → { access_token, refresh_token, … } |
| Call the Fanvue API | GET {API}/users/me — Authorization: Bearer <access token> |
| Refresh later | POST {HYDRA}/oauth2/token — client_secret_basic, grant_type=refresh_token, refresh_token |
{PLATFORM}, {HYDRA}, and {API} are the environment base URLs above.
Frontend (the iframe page)
Fanvue loads your Embed URL with the session token as a query param (?token=…), plus the creator’s active colour scheme as ?theme=light|dark. Read the token and hand it to your backend — and nowhere else. The theme is yours to use client-side for styling.
Backend (the delegated flow)
Node 18+ (built-infetch/crypto), framework-agnostic:
Using and refreshing tokens later (background work)
Store the refresh token per creator. When the access token expires, mint a new one — no iframe, no session token needed:Next steps
Publishing Your App
Submit your embedded app for review and go live on the App Store.
API Reference
Every endpoint you can call once you hold an access token.