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

> Payload reference for app.subscription.activated and cancel_at_period_end_changed, and why you read the period end from the API instead.

Two events share the `subscription` resource and report access-state changes on subscriptions to your app's pricing plans.

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

Every event requires `read:self`. Each arrives in the [envelope](/docs/webhooks/index#event-envelope), and the table below describes `data`. Fanvue records a cancellation as `cancel_at_period_end`, not an immediate removal, so 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/webhooks/app/payments) with `billing_reason: "subscription_initial"` and `app.subscription.activated`.

<Warning>
  Neither event tells you when access ends. `app.subscription.deactivated` is a reserved name that is never emitted, and `expires_at` and `created_at` are `null` on both events. Read period end from [`GET /apps/{appUuid}/subscription-status`](/docs/payments/app-billing/subscription-status) when either event arrives; deriving access from these events alone leaves access open after the period ends.
</Warning>

## Subscription resource

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

| Field | Type | Description |
| - | - | - |
| `object` | string | Always `"subscription"` |
| `id` | string | Subscription id |
| `status` | string | `"active"` on both emitted events. `expired` belongs to the reserved `app.subscription.deactivated`; `cancelled` is never sent |
| `cancel_at_period_end` | boolean | `false` on `activated`, `true` on `cancel_at_period_end_changed`. This field tells you the buyer cancelled |
| `expires_at` | string \| null | ISO 8601 end of the current period. `null` on both events |
| `created_at` | string \| null | ISO 8601 time the subscription started. `null` on both events |
| `plan` | object | `{ uuid }` of the subscribed pricing plan. `uuid` is set on `activated` and `null` on `cancel_at_period_end_changed` |
| `provider_subscription_id` | string \| null | Upstream provider subscription id, when any |
| `app` | object | `{ uuid }` of your app |
| `buyer` | object | `{ uuid }` of the buyer |
| `metadata` | object | `client_metadata` captured with the subscription, string keys and values. `{}` when none was set |

Branch on `cancel_at_period_end`, not on `status`. `status` is `"active"` on both events, including the cancellation, because a cancelled subscription stays active until its period ends, so a consumer switching on `status` sees no change when a buyer cancels.

## 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"` and the buyer keeps access until the period ends. `plan.uuid` is `null` 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

Subscription events report that something changed, not when the period ends. Use each event as the trigger to re-read [`GET /apps/{appUuid}/subscription-status`](/docs/payments/app-billing/subscription-status), which returns the current period end. The event `id` hashes the subscription id and the topic, so the same transition repeated on one subscription reuses the id. See [Repeated subscription state transitions](/docs/webhooks/delivery-and-idempotency#repeated-subscription-state-transitions).


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