Delivery guarantees
Return
2xx as soon as you have persisted the event, then process it asynchronously. Doing the work before you respond risks a timeout, which produces a retry and therefore a duplicate.
Retries
Fanvue retries failed deliveries with backoff. Every attempt for one event carries the sameid, so collapsing on it removes retries.
Once the retries are exhausted the event is not delivered again, and there is no manual replay. Treat a sustained failure as data loss and reconcile from the API.
Fanvue also drops a duplicate id emitted within a short window before delivery, so a re-emit of the same event from Fanvue’s side normally reaches you once.
Endpoint auto-disable
A destination that keeps failing is disabled and stops receiving events. The app owner is notified in the Developer Area. Re-enable the destination with its toggle in the Events & endpoints list. Subscriptions created withPOST /webhooks/subscriptions receive no notification, and GET /webhooks/subscriptions has no enabled flag. Detect silence yourself, then delete the subscription and create a new one.
Events that occurred while a destination was disabled are not backfilled. Reconcile the gap from the relevant API endpoints.
Deduplicating events
Every delivery carries an event id:- On the envelope events (
creator.*,checkout_link.*,payout.*,app.*) it is the top-levelid. - On the legacy flat events it is the
eventIdfield appended to the body.
- Every delivery attempt for the same event carries the same id, so collapsing on it removes retries.
- Two distinct events carry different ids, even for the same fan and creator, so collapsing on it does not remove real events.
Read receipts
A read receipt reports a count for a conversation rather than a specific message, so the read topics key their ids differently. The legacyeventId is derived from the creator, the fan and the event type only, so it stays the same for a conversation indefinitely. Pair it with timestamp as the occurrence component:
creator.message.read keys on the creator, the fan and the time the read was handled, so every occurrence has its own id and a retry of the same occurrence keeps it. The id alone is a valid key.
creator.message.read_by_fan keys on the creator, the fan and the newest message in the conversation. A fan cannot mark a conversation unread, so consecutive receipts without a new message in between don’t occur, and the id alone is a valid key. See Read-by-fan receipt resource.
creator.chat.marked_unread keys on marked_at, so every mark is a distinct id; see Deduplicating a mark.
Repeated subscription state transitions
app.subscription.* ids hash the subscription id and the topic, so the same transition repeated for one subscription reuses the earlier id. A cancel, a reactivation and a second cancel produce two app.subscription.cancel_at_period_end_changed events with one id, and the second is dropped. Pair the id with timestamp on these topics, or read the current state back from GET /apps/{appUuid}/subscription-status rather than tracking it from the event stream alone.
creator.subscription.* and creator.experience_subscription.* fold the time of the transition into the id, so a lapse and a later resubscription carry distinct ids and the id alone is a valid key.