fan_status resource and are discriminated by change_type, but ride separate topics so you can subscribe to one without the other.
Neither has a legacy flat equivalent. Both require the
read:chat scope, and presence events are sent only for creators who have presence enabled. Each event arrives in the envelope, and the tables below describe data.
Fan status resource
Fan object
handle and display_name are omitted from the payload rather than sent as null, so treat an absent key as “not resolved”. The message and payment events use the same convention. Both are public profile fields, and no email is carried on any creator.* event.
The identity lookup is best-effort and runs after the delivery decision. A lookup failure omits handle and display_name rather than delaying or dropping the event, so don’t treat their absence as a change to the fan’s profile.
Examples
creator.fan.status_changed (mute)
creator.fan.presence_changed (online)
creator.fan.presence_changed (offline)
Identical apart from state. changed_at is when the platform stopped considering the fan online, not the moment they closed the app.
Delivery
Both topics use the same delivery rule. Each event reaches:- apps holding
read:chatfor the creator, and - the creator’s connector destination, when one is subscribed.
read:chat doesn’t opt you into presence; subscribe to creator.fan.presence_changed explicitly. Presence is much higher volume than mute, with one event per fan per online/offline transition for every creator that fan is a contact of.
Mute changes, by contrast, fire for every creator on every mute or unmute action.
When presence fires
onlinefires when the fan’s first socket connects. Repeated connections while already online don’t re-fire.offlinefires when the fan’s presence record lapses. There is no disconnect signal to key off, soofflineis reported when the platform stops considering the fan online, which is later than the moment they actually left.
Treat presence as a hint, not a ledger
Presence describes state that is true only for a moment, and webhook delivery retries. A redeliveredonline can arrive after the offline that superseded it. Build for that rather than assuming ordered, exactly-once delivery:
- Compare
changed_atagainst the state you already hold and discard anything older. Don’t apply an event just because it arrived. - Reconcile from
GET /creators/{creatorUserUuid}/subscribers/onlineon reconnect, and periodically if presence drives anything a user sees. That endpoint is the source of truth, and the event tells you when to look. - Expect
offlineto be missed more often thanonline. Don’t build a session timer that depends on receiving both halves.