- an on-platform app whose creator surface already works, as in Run your app inside Fanvue
- a stored app access token with the
read:experiencescope, as in Store tokens - a public
httpsdomain of your own for the fan surface
Declare the URL
Declare your fan experience URL before the first publish. Set it in Embed settings in the Developer Area, or in your manifest asaccess.fanExperienceUrl with a surfaces[] entry whose surface is fan_experience. Fans can’t open the surface until an experience exists, which is what Publish experiences covers.
The URL must be https on a public domain of your own. Fanvue refuses Fanvue domains, localhost, IP addresses, dev tunnels and preview deploys.
Query parameters
The SDK reads the first two with
getSessionTokenFromUrl and getThemeFromUrl. It has no reader for presentation, so read that one with URLSearchParams.
Exchange the token
Exchange the token from your server withPOST /experiences/token/exchange. The call needs the read:experience scope, and the token works only with the credentials of the app that owns the experience. The SDK version below also signs a fan session for your own routes.
200 response
walletBalance is the fan’s wallet balance in USD cents, or null. null means the fan has no wallet at all, not an empty one, so don’t offer a wallet charge in that case. The balance is a snapshot, never an authorisation.
HIDDEN experiences skip the entitlement check at exchange, because holding a valid token is the share-link capability. Their entitlement.reason reads hidden_token_exchange.
The SDK differs from the raw endpoint in four ways:
createFanSessionHandleranswers a denial as 200{ entitled: false, accessMode, reason }, so your page can render a locked screen.- It limits the route to 30 requests per minute per IP. The creator session route allows 10.
client.experiences.exchangeExperienceTokenreturns one of seven outcomes:entitled,denied,expired,binding_mismatch,unavailable,rate_limitedandupstream_error.- The SDK schema drops
walletBalance, so call the endpoint directly if you need the balance.
requireFanSession(request, { sessions }), which rejects a missing or revoked fan session with 401.
Branch on entitlement
Branch onisEntitled, not on reason, because the list of reasons grows without an API version change.
Fanvue reads purchases from the payment ledger on every launch, so a refund or chargeback revokes access on the fan’s next launch.
Sandbox and headers
Fanvue renders your surface with the sandboxallow-scripts allow-forms allow-popups allow-same-origin. It adds allow-same-origin at render time, after checking that your origin isn’t a Fanvue origin, so your cookies, storage and same-origin API calls work.
allow-modals and allow-downloads are absent. window.alert and window.confirm do nothing, and downloads are blocked inside the frame and in any tab it opens.
Your response must carry Content-Security-Policy: frame-ancestors 'self' https://fanvue.com https://*.fanvue.com. Hosting lists the full header set.
Page or dialog
When a fan clicks an experience card on a chat or a profile,presentation on your fan_experience surface decides whether it opens as a full page or in a dialog over the chat or profile.
app-manifest.json excerpt
typeispage(the default) ordialog.desktopWidthanddesktopHeightare integers from 240 to 1600 CSS pixels, both or neither. Both omitted means as large as the viewport allows.- Phones always show a dialog full screen.
- On desktop the sizes describe your content. Fanvue adds its own chrome and clamps the dialog to the viewport, so measure your real size from the window rather than assuming the declared one.
- Direct links and the detail page always open the page.
{ type: "fanvue:experience:close-request" } to window.parent. Fanvue honours it only in a dialog, under the same origin and rate rules as the other fan bridges. Send it when the fan presses Escape inside your frame, because key events in a cross-origin iframe never reach Fanvue.
presentation in the manifest.
External delivery
EXTERNAL experiences show an Open button with a note that your app opens in a new tab outside Fanvue. The fan’s click opens externalUrl in a new tab with ?token= appended. Exchange the token the same way; nothing else differs.