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

# Payments inside experiences

> How fans pay inside a Fanvue experience: paid and recurring access, priced actions, the purchase bridge, wallet charges, fulfilment and refunds.

Fans can pay for an experience in two ways: they buy access to it, or they buy priced actions inside it, such as a spin or a reveal. Every charge is between the fan and the creator. Your app declares what can be bought, triggers the charge and fulfils on the webhook. You need a published experience (see [Publish experiences](/docs/app-store/experiences/publish)) and a webhook endpoint that receives App events.

For priced actions there are two charge paths:

| | Purchase bridge | Wallet charge |
| - | - | - |
| Started by | Your fan surface, when the fan acts | Your server |
| What the fan sees | Fanvue's native payment dialog | No dialog |
| Requires | A `fanvue:experience:purchase-request` message | `write:experience`, a live spend consent and an `Idempotency-Key` |

## Who gets paid

The creator is the seller on every experience invoice, at the standard Fanvue fee. Your app isn't a party to the charge and receives no share. Every price is in USD cents, from 300 to 50000. What each [access mode](/docs/app-store/experiences/overview#access-modes) charges, and when it renews:

| `accessMode` | Charge | Renews |
| - | - | - |
| `FREE` | none | no |
| `SUBSCRIPTION` | none; access derives from the fan's profile subscription to the creator | with the profile subscription |
| `PAID`, `priceRecurring: false` | `priceCents` once, in Fanvue's native checkout | no |
| `PAID`, `priceRecurring: true` | `priceCents` every month, as an app subscription | yes, at the original price |

## Paid access

Fans buy `PAID` access in Fanvue's native checkout from the detail page, a chat card or the experience dialog. The `purchase.new` webhook carries `experienceUuid` and `experienceAppUuid`, and so does every row of `GET /insights/earnings` for the experience.

Recurring access mints a monthly price, and subscriptions renew automatically at the original price. Renewal pauses while the app is suspended, and stops when:

* the experience is unpublished
* the experience is no longer `PAID`
* the experience is no longer recurring
* the app is uninstalled

A failed renewal enters dunning with no experience-specific grace. The `creator.experience_subscription.activated` and `creator.experience_subscription.deactivated` webhooks report the lifecycle; see [Creator events: experience subscriptions](/docs/creator/experience-subscriptions).

`SUBSCRIPTION` access produces no charge and no webhook of its own.

## Priced actions

A priced action is something a fan buys inside your experience, such as a spin or a reveal. Each experience carries a catalogue of up to 10. The creator prices them, in the confirmation dialog or through a `price-request` from your creator surface, unless your app prices them in a direct publish.

`GET /experiences/{experienceUuid}/actions` (`read:experience`) returns the live catalogue, re-priced on every read.

```bash theme={null}
curl https://api.fanvue.com/experiences/e5a1c3d7-9b2f-4e6a-8c4d-1f7b3a9e2d58/actions \
  -H "Authorization: Bearer <token>" \
  -H "X-Fanvue-API-Version: 2025-06-26"
```

```json theme={null}
{
  "actions": [
    { "externalActionId": "critique", "title": "Critique my photo", "status": "active", "amount": 700, "currency": "USD" }
  ]
}
```

`title` is the creator-facing label shown in the payment dialog. `status` is always `active`, because a withdrawn action is omitted rather than returned as disabled.

`GET /apps/{appUuid}/experience-action-definitions` (`read:experience`) returns the actions your app has declared, without any amount. It answers 404 when the creator hasn't installed your app.

## Charge path 1: the purchase bridge

Your fan surface posts a purchase request and Fanvue opens its native payment dialog. The request never carries an amount, because the price comes from the catalogue.

```ts theme={null}
window.parent.postMessage(
  { type: "fanvue:experience:purchase-request", externalActionId: "critique", clientReferenceId: "round-42" },
  "*"
);

window.addEventListener("message", (event) => {
  if (!/^https:\/\/([a-z0-9-]+\.)*fanvue\.com$/.test(event.origin)) return;
  if (event.data?.type === "fanvue:experience:purchase-result") {
    // { status: "succeeded" | "failed" | "cancelled", externalActionId, clientReferenceId?, purchaseReference? }
    // UX only: stop the spinner. Fulfil on the webhook.
  }
});
```

Two more fan bridges support the wallet:

* `fanvue:experience:topup-request` with `{ amountMinorUnits, clientReferenceId? }` opens the top-up dialog and answers `topup-result` with `{ status, clientReferenceId?, invoiceNumber? }`.
* `fanvue:experience:consent-request` with `{ clientReferenceId? }` asks for spend consent and answers `consent-result` with `{ status: "granted" | "declined" | "failed" }`. A fan who has already consented gets `granted` with no dialog.

[Fan surface and creator surface messages](/docs/app-store/experiences/messages) lists every message.

## Charge path 2: wallet charge from your server

`POST /experiences/{experienceUuid}/action-purchases` (`write:experience`) charges one action to the fan's wallet with no dialog. The `Idempotency-Key` header is required.

```bash theme={null}
curl -X POST https://api.fanvue.com/experiences/e5a1c3d7-9b2f-4e6a-8c4d-1f7b3a9e2d58/action-purchases \
  -H "Authorization: Bearer <token>" \
  -H "X-Fanvue-API-Version: 2025-06-26" \
  -H "Idempotency-Key: round-42-critique" \
  -H "Content-Type: application/json" \
  -d '{ "externalActionId": "critique", "fanUuid": "4d8f2b6e-1c3a-4b9d-a7e5-6f0c2d8b1a93", "clientReferenceId": "round-42" }'
```

A 201 returns the purchase plus `walletBalance`, the fan's remaining balance in USD cents. Purchase references are prefixed `expact_`.

Idempotency keys are scoped to your app, the fan and the experience, and remembered for 24 hours. Reusing a key replays the first charge once it has settled.

The charge needs a live spend consent for this app and experience. The fan grants it by topping up inside the experience or by answering `consent-request`, and can revoke it.

| Status | Body | Meaning |
| - | - | - |
| 400 | `{ message }` | `Idempotency-Key` missing or longer than 255 characters |
| 402 | `{ error: "insufficient_balance", message, balance, amount }` | Balance does not cover the action; nothing charged, nothing recorded |
| 403 | `{ error: "consent_required", message }` | No live spend consent for this experience |
| 403 | `{ error: "payer_ineligible", message }` | The fan cannot be charged: account not active, payments blocked, blocked by the creator, or age verification the creator requires not passed |
| 409 | `{ message }` | Same key still running, or reused for a different charge |
| 503 | `{ message }` | The creator's account does not allow wallet charges |

## Fulfil on the webhook

Fulfil only on `app.experience.action.payment.succeeded`, keyed on `purchase_reference`, so a redelivery grants nothing twice. The bridge result is never proof of payment. For what to do on each status, including when to revoke a grant, see the [fulfilment rule](/docs/webhooks/app/experience-actions#fulfilment-rule).

Six events cover the lifecycle, under the `read:self` scope: `app.experience.action.payment.pending`, `app.experience.action.payment.succeeded`, `app.experience.action.payment.failed`, `app.experience.action.refund.created`, `app.experience.action.dispute.flagged` and `app.experience.action.dispute.created`. [App events: experience actions](/docs/webhooks/app/experience-actions) has the payloads.

## Reconcile

`GET /experiences/{experienceUuid}/action-purchases` lists every attempt, newest first, with keyset paging through `nextCursor` (`limit` default 50, max 100). `GET /experiences/{experienceUuid}/action-purchases/{purchaseReference}` reads one.

```bash theme={null}
curl "https://api.fanvue.com/experiences/e5a1c3d7-9b2f-4e6a-8c4d-1f7b3a9e2d58/action-purchases?limit=50" \
  -H "Authorization: Bearer <token>" \
  -H "X-Fanvue-API-Version: 2025-06-26"
```

## Refunds

Refunds have no API. Fanvue re-derives access from the payment ledger on every launch, so a refunded unlock loses access on the fan's next launch. A refunded action reports `refunded` on the webhook and on the purchase reads.

## See also

* [Payments overview](/docs/payments/overview)


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.