Notes from building against them: the event catalogue, what the payloads actually contain, and the delivery behaviour that will bite you if you assume otherwise.
OAuth 2.0, authorization code with PKCE — mandatory, not optional. Access tokens last about an hour. Refresh tokens rotate and are single use: replaying one after its thirty-second grace window invalidates the entire chain and forces the creator to re-authorise.
Two workers refreshing the same connection concurrently is therefore not a lost update, it is a broken integration. Serialise it.
The creator.* family supersedes the older flat events and shares
one Standard-Webhooks envelope:
{
"id": "f1a2b3c4-…",
"type": "creator.subscription.activated",
"timestamp": "2026-06-09T08:39:33.139Z",
"data": { "object": "subscription", … }
}
Fields inside data are snake_case, and optional values are
omitted rather than sent as null — so reading
payload.data.media_uuids.length will throw rather than return zero.
X-Fanvue-Signature: t=<unix>,v0=<hex>
HMAC-SHA256 over {timestamp}.{rawBody}, using the raw bytes.
Parsing the JSON and re-serialising it changes those bytes and the signature will
not match. Reject anything older than about five minutes, or an old but genuinely
signed delivery can be replayed.
Two registration paths exist with different secrets: a per-app secret for
webhooks added in the Developer Area, and a fresh per-subscription secret from
POST /webhooks/subscriptions — returned once, never readable again.
| Property | Behaviour |
|---|---|
| Delivery | At least once. Duplicates are expected |
| Ordering | Not guaranteed |
| Timeout | 10 seconds |
| Retries | 5, at 30-second intervals |
| Auto-disable | After 20 consecutive failures |
| Backfill | None. Events missed while disabled are gone |
Persist and return 2xx immediately, then process asynchronously.
Doing the work first risks a timeout, which produces a retry, which produces a
duplicate. And acknowledge event types you do not recognise — a 4xx
counts as a failure and walks a healthy endpoint towards auto-disable.
The top-level id is a deterministic hash of the event's
identifying inputs, so it is a valid idempotency key almost everywhere. Two
topics are exceptions:
Not populated uniformly, which is easy to get wrong:
tracking — appears on payments, subscriptions and follows,
carrying link_url and per-impression metadata.
Present on the first touch, null on renewals.client_reference_id and metadata — real values on
subscription and checkout events; always null or empty on
creator.payment.succeeded.purchaser.email — always null. Documented as
present for shape parity only.That last one shapes every integration built on this API: identity resolution
cannot rely on email, so whatever identifier you need later has to be sent out
with the visitor and read back from tracking.metadata.
All amounts are integers in the currency's minor units, with a separate ISO 4217 code that may be null. Subscription events carry a price but no currency; the correlated payment event carries both.
EvlPixel already handles the webhooks, the token rotation and the attribution join.
Start tracking