read:chat and an HTTPS webhook endpoint you can register on your app’s Events tab.
The plan
1
Backfill once
On first connect, walk each chat with
beforeMessageUuid until hasMore is false, or seed from POST /chats/messages/batch in pages of 50 chats if you don’t need mass messages.2
Stay live with webhooks
Subscribe to
creator.message.received and creator.message.read. Steady state costs no requests.3
Reconcile after gaps
If your endpoint was down, request each active chat with
startDate set to your newest stored sentAt, or run one batch walk with sinceMessageUuid. Then return to webhooks.Receive messages with webhooks
Webhooks handle the steady state. Subscribe tocreator.message.received, and to creator.message.read if you track read state, and Fanvue posts each event to your webhook endpoint as it happens, at zero steady-state request cost.
1
Enable the read:chat scope
Both events require the
read:chat scope. Add it on your app’s Authentication tab, or to oauth.scopes in your App Manifest. Users who connected before you added the scope must re-authorise. See Scopes.2
Register your endpoint
In the Developer Area, open your app’s Events tab, add your HTTPS endpoint URL and select the two events. See Subscribe to webhooks.
3
Verify every signature
Verify
X-Fanvue-Signature on every delivery so forged or replayed events are rejected. See Verify webhook signatures.4
Persist and return 2xx fast
Return a 2xx as soon as you have persisted the event, and do slow processing asynchronously. Fanvue retries failed deliveries with backoff; see Delivery and idempotency.
creator.message.received payload contains the message, sender and recipient, so most surfaces never call the API back. See Creator message events for the envelope.
Webhooks don’t cover the initial backfill or a gap after downtime, so you still need the two pull paths below.
Backfill history with beforeMessageUuid
GET /chats/{userUuid}/messages returns messages newest first and accepts a keyset cursor. Pass beforeMessageUuid to fetch the messages immediately older than a known message. The cursor takes precedence over page, and an unknown anchor is ignored and returns the first page. Set the page size with size (default 15, maximum 50).
- Call without a cursor to get the newest 50 messages.
- Take the
uuidof the last item indataand pass it asbeforeMessageUuid. - Repeat until
pagination.hasMoreisfalse.
markAsRead=false on every backfill call. The default is true, and a read-only sync mustn’t clear the creator’s unread state.
The per-chat thread includes BROADCAST mass messages the creator sent to this fan, interleaved by publish date. POST /chats/messages/batch returns direct messages only, so the two endpoints return different message sets for the same chat.
Catch up after a gap with startDate
The same endpoint acceptsstartDate and endDate, both ISO 8601 with an optional offset and both exclusive. startDate returns messages published strictly after it, and endDate returns messages published strictly before it. Both combine with page and size.
sentAt of the newest message you hold per creator. After downtime, request each active chat with startDate set to that value and page until hasMore is false.
endDate suppresses markAsRead. An upper-bounded window leaves out the newest messages, so a request carrying endDate never marks the chat read, whatever markAsRead says.Pull many chats in one request
POST /chats/messages/batch fetches up to 50 chats in one request and supports an incremental cursor. Use it when you reconcile many chats at once and don’t need mass messages.
The response is keyed by chat UUID under
byChat. Each entry is either a success (messages, plus hasMore and oldestMessageUuid) or a per-key error ({ "error": "not_found" } for a chat that doesn’t exist or that you aren’t part of). One failing chat never fails the request, so keep the rest and retry the failing keys. The batch endpoint never marks messages read.
Incremental delta walk
Capacity planning
Why not poll
Re-polling every chat for new messages on a fixed interval is the most common cause of throttling:creators × chats × poll frequency, and most of those requests return no new data. The pattern exhausts your rate-limit budget, and it’s the first thing to fix when you see sustained 429 responses.
A worked budget
Take an agency with 100 creators and 50 active chats each:- Polling every 45 seconds:
100 × 50reads every 45 seconds is about 6,600 requests per minute, far past the default 200 per minute per user per app, and most of it returns nothing. - Webhooks plus reconciliation: 0 requests per minute in steady state. A catch-up walk batches 50 chats per request, so reconciling all 5,000 chats costs about 100 requests, run only when needed.
- Creator-scoped routes get a bucket per creator. Per-creator work, including
POST /creators/{creatorUserUuid}/chats/messages/batch, draws from its own 200 per minute budget, so throughput grows with your creator count. - Aggregate
/agencies/*routes share one bucket. Reserve it for cross-creator queries, not per-message sync.
429 responses continue after you move to webhooks, something else is polling, for example online-status checks or insights refreshes. Audit which clientId:userUuid keys spend budget and apply the same delta and bulk rules.