Skip to main content
By the end of this page you’ll have a local copy of a creator’s chats that backfills its history once, stays current through webhooks and catches up after downtime, without spending your rate limit on polling. You need an access token with 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.
The sections below cover each part, then what the plan saves you compared with polling.

Receive messages with webhooks

Webhooks handle the steady state. Subscribe to creator.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.
The 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).
Backfill loop, per chat:
  1. Call without a cursor to get the newest 50 messages.
  2. Take the uuid of the last item in data and pass it as beforeMessageUuid.
  3. Repeat until pagination.hasMore is false.
Pass 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 accepts startDate 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.
Store the 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:
Request volume scales with 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 × 50 reads 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.
Two facts from the rate-limit keying rules make this scale:
  • 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.
If 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.