> ## 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.

# App Subscription Events

> Reference for app.subscription.activated and app.subscription.cancel_at_period_end_changed webhook payloads, fields, and access-state semantics for your app.

Two events share the `subscription` resource. They are delivered in the
[Standard-Webhooks envelope](/docs/app-store/webhooks#event-envelope); the fields
below describe the `data` object. Every event requires the `read:self` scope.

| Event (`type`)                                  | Fires when                                                  |
| ----------------------------------------------- | ----------------------------------------------------------- |
| `app.subscription.activated`                    | A subscription becomes active                               |
| `app.subscription.cancel_at_period_end_changed` | Auto-renew is turned off; access continues until period end |

Fanvue models cancellation as `cancel_at_period_end`, not an immediate removal: a
buyer who cancels keeps access until the end of the paid period.

The money and the access state are separate events: a subscription's initial
charge emits **both**
[`app.payment.succeeded`](/docs/app-store/webhooks/payments) (with
`billing_reason: subscription_initial`) **and** `app.subscription.activated`.

<Warning>
  **These two events do not tell you when access ends.**
  `app.subscription.deactivated` is a reserved name that is **not emitted**, and
  neither of the events below carries a date: `expires_at` and `created_at` are
  **always `null`** today, because Fanvue's billing source does not supply them
  to the webhook. Treat these events as a signal to re-read live state from the
  [App Subscriptions](/docs/app-store/app-subscriptions) endpoints — they are the only
  place the period dates exist.
</Warning>

## Subscription resource

`data.object` is `"subscription"` for both events.

| Field                      | Type           | Description                                                                                                                                   |
| -------------------------- | -------------- | --------------------------------------------------------------------------------------------------------------------------------------------- |
| `object`                   | string         | Always `"subscription"`                                                                                                                       |
| `id`                       | string         | Subscription id                                                                                                                               |
| `status`                   | string         | **Always `"active"`** on both events below. `expired` is reserved for the unemitted `app.subscription.deactivated`; `cancelled` is never sent |
| `cancel_at_period_end`     | boolean        | `false` on `activated`, `true` on `cancel_at_period_end_changed`. This is the field that tells you the buyer cancelled                        |
| `expires_at`               | string \| null | ISO 8601 end of the current period. **Always `null`** today                                                                                   |
| `created_at`               | string \| null | ISO 8601 time the subscription started. **Always `null`** today                                                                               |
| `plan`                     | object         | `{ uuid }` of the subscribed pricing plan. Populated on `activated`; **`null`** on `cancel_at_period_end_changed`                             |
| `provider_subscription_id` | string \| null | Upstream provider subscription id, if any                                                                                                     |
| `app`                      | object         | `{ uuid }` of your app                                                                                                                        |
| `buyer`                    | object         | `{ uuid }` of the buyer                                                                                                                       |
| `metadata`                 | object         | Reserved passthrough metadata map (currently always empty)                                                                                    |

<Note>
  **Branch on `cancel_at_period_end`, not on `status`.** `status` is `"active"`
  on both events, including the cancellation — a cancelled subscription is still
  active until its period ends. A consumer switching on `status` sees no change
  at all when a buyer cancels.
</Note>

## Examples

### `app.subscription.activated`

```json theme={null}
{
  "id": "3d4e5f60718293a4b5c6d7e8f9a0b1c2d3e4f5a6b7c8d9e0f1a2b3c4d5e6f708",
  "type": "app.subscription.activated",
  "timestamp": "2026-06-17T13:12:45.200Z",
  "data": {
    "object": "subscription",
    "id": "3f9a2b71-1c4e-4f8a-9d2b-7c6e5a4b3d21",
    "status": "active",
    "cancel_at_period_end": false,
    "expires_at": null,
    "created_at": null,
    "plan": { "uuid": "b2d7c9f0-4a13-4e6b-8f25-1a9c3e7d5b80" },
    "provider_subscription_id": "psub_8a0f2d4b6c8e",
    "app": { "uuid": "a1c3e5f7-9b2d-4c6e-8a0f-2d4b6c8e0a13" },
    "buyer": { "uuid": "c4e6a8b0-2d4f-6a81-0c2e-4b6d8f0a2c46" },
    "metadata": {}
  }
}
```

### `app.subscription.cancel_at_period_end_changed`

`status` stays `"active"` — the buyer keeps access until the period ends — and
`plan.uuid` is not carried on this event.

```json theme={null}
{
  "id": "4e5f60718293a4b5c6d7e8f9a0b1c2d3e4f5a6b7c8d9e0f1a2b3c4d5e6f70819",
  "type": "app.subscription.cancel_at_period_end_changed",
  "timestamp": "2026-06-20T09:00:00.000Z",
  "data": {
    "object": "subscription",
    "id": "3f9a2b71-1c4e-4f8a-9d2b-7c6e5a4b3d21",
    "status": "active",
    "cancel_at_period_end": true,
    "expires_at": null,
    "created_at": null,
    "plan": { "uuid": null },
    "provider_subscription_id": "psub_8a0f2d4b6c8e",
    "app": { "uuid": "a1c3e5f7-9b2d-4c6e-8a0f-2d4b6c8e0a13" },
    "buyer": { "uuid": "c4e6a8b0-2d4f-6a81-0c2e-4b6d8f0a2c46" },
    "metadata": {}
  }
}
```

## Reconciling

These events tell you *that* something changed, not *when* the period ends.
Because `app.subscription.deactivated` is not emitted, `expires_at` and
`created_at` are always `null`, and webhooks can be missed or delayed, read live
subscription state from the [App
Subscriptions](/docs/app-store/app-subscriptions) endpoints rather than deriving it
from events alone. Use the event as the trigger to re-read.
