---
title: "Create a webhook subscription"
method: POST
path: "/webhooks/subscriptions"
---

# Create a webhook subscription

`POST /webhooks/subscriptions`

Subscribe a public HTTPS URL to one or more platform events. Every requested event is delivered to the same URL and signed with the single `signingSecret` returned in the response — store it, as it is only returned at creation time. The token must hold the OAuth scope each requested event needs: `read:creator` for `subscription.new`, `subscription.renewed`, `subscription.cancelled`, `subscription.expired`, `follow.new`, `purchase.new`, `tip.new`, the `checkout_link.*` and `payout.paid` events, and the `creator.payment.succeeded`, `creator.subscription.*`, `creator.refund.created`, and `creator.dispute.*` events; `read:post` for `creator.post.liked` and `creator.post.commented`; `read:chat` for `message.received`, `message.read`, `creator.message.received`, `creator.message.sent`, `creator.message.mass_sent`, `creator.message.read`, `creator.message.read_by_fan`, `creator.message.reaction`, `creator.message.deleted`, and `creator.chat.marked_unread`; `read:experience` for the `creator.experience_subscription.*` events.

`creator.experience_subscription.deactivated` fires whenever a fan's paid fan-experience subscription stops granting access — they cancelled and the period ran out, a renewal failed, the charge was refunded or charged back, or we or the creator ended it. Act on `access_ends_at`, the moment access was revoked, which is not always `expires_at` (a refund, chargeback, ban or unpublish ends access mid-period). A cancellation on its own does not fire it, because access continues to period end. Treat `expires_at` as a deadline in its own right rather than waiting only for this event: access also lapses on the clock when a renewal is still settling or an upstream lookup fails, and nothing is sent until a later hourly sweep resolves the subscription.

Pair it with `creator.experience_subscription.activated`, which fires when access starts: `activation_reason` is `subscribed` on the fan's initial purchase and `renewal_recovered` when a renewal charge that had already failed is recovered by a later retry. Handling the recovery case is required if you act on the deactivated event, because a failed renewal revokes access before the retries that may still pay for the period are exhausted.

The two read receipts are separate directions and neither replaces the other: `creator.message.read` fires when the creator reads the fan's messages, `creator.message.read_by_fan` when the fan reads the creator's. Their topic names the actor while the `object` in the payload (`fan_message_read` / `creator_message_read`) names whose messages were read, so the two names run on different axes.

`creator.chat.marked_unread` is the other half of the inbox badge: the creator deliberately marked a conversation unread, where `creator.message.read` is the event that clears it. It is conversation state, not message state — no message's read status changes, so do not revert the read ticks you took from `creator.message.read_by_fan`. `unread_messages_count` is the conversation's real unread count and is unchanged by the mark, so `0` is normal: render a chat as unread when the marker arrives OR that count is above zero. The two halves are delivered independently and are not ordered against each other, so compare `marked_at` with the `read_at` on `creator.message.read` and apply whichever is later rather than whichever arrives last. It reports the transition into unread, so re-marking a conversation that is already unread emits nothing.

Mass messages are delivered on `creator.message.mass_sent`, never on `creator.message.sent`, so subscribe to both to see every creator-to-fan send. The event describes the send, not each copy of it: one `mass_message` per broadcast, carrying the broadcast's `uuid`, its `message_type` and `recipient_count`. There is no `fan` — a broadcast has no single recipient — and `text` is the authored body with its personalisation tokens (`{{handle}}`) left intact, because there is no one recipient to render it for. Substitute per fan if you need the rendered text. Which fans received a send is not carried on the event; that list is not yet exposed on the API.

## Headers

- `X-Fanvue-API-Version` string, required

## Request body

- object
  - `url` string, uri, required — Public HTTPS URL the event payloads are delivered to (no redirects, no localhost)
  - `events` string[], required — One or more platform events this subscription delivers

## Response `201`

Webhook subscription created successfully

- object
  - `id` string, uuid, required — ID of the created webhook subscription
  - `signingSecret` string, required — HMAC signing secret for verifying delivery signatures. Returned only on creation — store it securely now.

## Other responses

- `400` — Bad Request - API version not supported OR validation failed
- `401` — Unauthorized Response
- `403` — Unauthorized Response
- `410` — API version no longer supported (sunset)
- `429` — Too many requests - rate limit exceeded

## Changes

- **2026-09-18** `7b1c33d6c706` — 1 info
  - added the new `creator.chat.marked_unread` enum value to the request property `events/items/`
- **2026-09-10** `4d08f36ad6c8` — 1 info
  - added the new `checkout_link.refund.requested` enum value to the request property `events/items/`
- **2026-08-30** `dce5621fe589` — 6 info
  - added the new `creator.experience_subscription.activated` enum value to the request property `events/items/`
  - added the new `creator.experience_subscription.deactivated` enum value to the request property `events/items/`
  - added the new `creator.fan.presence_changed` enum value to the request property `events/items/`
  - added the new `creator.message.mass_sent` enum value to the request property `events/items/`
  - …2 more

[Change history](https://skmtc.dev/fanvue/apis/fanvue-api/changes/webhooks/subscriptions/post.md)

---

[API](https://skmtc.dev/fanvue/apis/fanvue-api.md) · [All operations](https://skmtc.dev/fanvue/apis/fanvue-api/llms.txt) · [OpenAPI document](https://skmtc.dev/fanvue/apis/fanvue-api/revisions/431868e8a264?raw)
