When a buyer purchases one of your app’s one-time items, or that purchase is
later refunded, charged back, or cancelled, Fanvue sends a webhook to your app so
you can fulfil and reconcile the order. There are exactly two events:
Event (type) | Fires when |
|---|
app.payment.succeeded | A one-time (appotp_) purchase of your app is paid |
app.payment.refunded | A one-time (appotp_) purchase is refunded, charged back, or cancelled |
These webhooks cover one-time purchases only, identified by a
purchase_reference with the appotp_ prefix. App subscriptions do not
emit webhooks; read subscription state with the App
Subscriptions endpoints instead.
Delivery model
Unlike the creator checkout webhooks, you do not subscribe to these events.
Fanvue delivers them automatically to your app’s own webhook tenant as soon
as your app has a paid one-time item, so there is no connector API call to set up
a subscription.
This is a different delivery channel from the creator-tenant webhooks: the
checkout webhooks and
platform event webhooks are delivered to the
creator’s tenant, whereas app.payment.* events are delivered to the
purchased app’s tenant.
There is no app.payment.failed event. A failed one-time purchase is
terminal, the buyer simply retries, so nothing is delivered for a failure.
Likewise there is no app.payment.pending.
Event envelope
Every app payment webhook is delivered as an HTTP POST with the same
Standard-Webhooks-style envelope used by checkout webhooks:
{
"id": "<event id>",
"type": "app.payment.succeeded",
"timestamp": "2026-06-17T13:12:45.123Z",
"data": { "object": "payment", "...": "resource fields" }
}
id: unique event id, stable across delivery retries (use it to dedupe).
type: app.payment.succeeded or app.payment.refunded.
timestamp: ISO 8601 time the event was emitted.
data: the resource object. data.object is "payment" for a succeeded
purchase and "refund" for a refund.
Monetary amounts are integers in the currency’s minor units (for USD, cents,
so 999 means $9.99).
app.payment.succeeded
data.object is "payment".
| Field | Type | Description |
|---|
object | string | Always "payment" |
id | string | Fanvue invoice number for the payment |
status | string | Always "succeeded" |
gross | integer | Gross amount in minor units |
currency | string | ISO 4217 currency code |
purchase_reference | string | The one-time purchase reference (appotp_ prefix) |
client_reference_id | string | null | Your passthrough reference from the buyer flow |
created_at | string | ISO 8601 creation time |
paid_at | string | ISO 8601 time the payment was paid |
item | object | null | { uuid } of the purchased pricing plan (uuid may be null) |
app | object | { uuid } of your app |
buyer | object | { uuid } of the buyer |
metadata | object | null | Passthrough metadata echoed from the buyer flow (may be null or empty) |
{
"id": "9f2c1e7a4b8d6f30a1c2e3d4b5a6978c0e1f2a3b4c5d6e7f8a9b0c1d2e3f4a5b",
"type": "app.payment.succeeded",
"timestamp": "2026-06-17T13:12:45.123Z",
"data": {
"object": "payment",
"id": "INV-2026-000123",
"status": "succeeded",
"gross": 999,
"currency": "USD",
"purchase_reference": "appotp_3f9a2b71-1c4e-4f8a-9d2b-7c6e5a4b3d21",
"client_reference_id": "your-app-order-42",
"created_at": "2026-06-17T13:12:40.000Z",
"paid_at": "2026-06-17T13:12:44.880Z",
"item": { "uuid": "b2d7c9f0-4a13-4e6b-8f25-1a9c3e7d5b80" },
"app": { "uuid": "a1c3e5f7-9b2d-4c6e-8a0f-2d4b6c8e0a13" },
"buyer": { "uuid": "c4e6a8b0-2d4f-6a81-0c2e-4b6d8f0a2c46" }
}
}
app.payment.refunded
data.object is "refund". The refund resource references the original payment
via payment_id, carries amount (not gross), and has no status or
paid_at.
| Field | Type | Description |
|---|
object | string | Always "refund" |
id | string | Fanvue invoice number for the refund |
payment_id | string | Invoice number of the original app.payment.succeeded payment |
amount | integer | Refunded amount in minor units |
currency | string | ISO 4217 currency code |
reason | string | refund | chargeback | cancel |
purchase_reference | string | The one-time purchase reference (appotp_ prefix) |
client_reference_id | string | null | Your passthrough reference from the buyer flow |
created_at | string | ISO 8601 creation time |
item | object | null | { uuid } of the purchased pricing plan (uuid may be null) |
app | object | { uuid } of your app |
buyer | object | { uuid } of the buyer |
metadata | object | null | Passthrough metadata echoed from the buyer flow (may be null or empty) |
{
"id": "1a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b1c2d3e4f5a6b7c8d9e0f1a2b",
"type": "app.payment.refunded",
"timestamp": "2026-06-18T09:30:00.000Z",
"data": {
"object": "refund",
"id": "INV-2026-000456",
"payment_id": "INV-2026-000123",
"amount": 999,
"currency": "USD",
"reason": "refund",
"purchase_reference": "appotp_3f9a2b71-1c4e-4f8a-9d2b-7c6e5a4b3d21",
"client_reference_id": "your-app-order-42",
"created_at": "2026-06-18T09:29:58.000Z",
"item": { "uuid": "b2d7c9f0-4a13-4e6b-8f25-1a9c3e7d5b80" },
"app": { "uuid": "a1c3e5f7-9b2d-4c6e-8a0f-2d4b6c8e0a13" },
"buyer": { "uuid": "c4e6a8b0-2d4f-6a81-0c2e-4b6d8f0a2c46" }
}
}
reason tells the three refund paths apart: refund (a normal refund),
chargeback (a dispute resolved against the sale), and cancel (the purchase
was cancelled). All three arrive as app.payment.refunded.
Verifying signatures
App payment webhooks are signed exactly like the rest of Fanvue’s webhooks, with
an X-Fanvue-Signature header (t=<timestamp>,v0=<hmac-sha256-hex>). Verify it
against the raw request body before parsing JSON. The header breakdown, the
verification flow, and complete Node and Python samples live in
Verify Webhook Signatures.
Reconciliation
Webhooks can be missed or delayed, so reconcile against the app one-time payments
REST endpoints. They are read-only, require the read:self scope, and split into
owner-scoped (all buyers of an app you own) and caller-scoped (/me, the
authenticated user’s own payments):
| Endpoint | Scope | Returns |
|---|
GET /apps/{appUuid}/payments | Owner | All buyers’ one-time payments for an app you own |
GET /apps/{appUuid}/payments/{invoiceNumber} | Owner | One payment by Fanvue invoice number |
GET /apps/{appUuid}/payments/me | Caller | The authenticated user’s own one-time payments for the app |
GET /apps/{appUuid}/payments/me/{invoiceNumber} | Caller | The caller’s own payment by invoice number |
The invoiceNumber path parameter matches data.id (for a payment) or
data.payment_id (for the original payment behind a refund).
These payment endpoints are not yet part of the generated API
Reference; they are documented here until they
appear in the OpenAPI spec.