OnlyFans API

Available Fansly webhook events

A list of all available Fansly webhook events that you can subscribe to.

Looking for a specific event?

Please reach out to us, we can add it!

All Fansly events are prefixed with fansly. so they never collide with the OnlyFans events. Delivery is driven by our Fansly websocket relay rather than polling, so events arrive in near real-time.

Fansly events only appear in the event picker once your team has a connected Fansly account. If you don't see them when subscribing to a webhook, connect an account first.

Payload conventions

Fansly events use the same envelope as the OnlyFans events: an event name, the account_id of the connected account, and a platform-native payload:

Envelope
{
  "event": "fansly.<category>.<name>",
  "account_id": "fansly_acct_123",
  "payload": { }
}

Within payload, Fansly's own conventions apply:

Money is in thousandths of a dollar

Unlike the OnlyFans events, Fansly amounts are not dollars and not cents. 15000 means $15.00. Divide by 1000 before displaying or storing an amount.

  • IDs are snowflake strings, not integers. Keep them as strings, because they exceed the safe integer range in JavaScript and other 53-bit float languages.
  • Timestamps are Unix epoch, in seconds or milliseconds depending on the upstream field (e.g. createdAt is seconds on fansly.posts.created, but milliseconds on fansly.subscriptions.new). Check the magnitude before parsing.
  • type fields are Fansly enums, not strings. Their meaning is specific to each event.

Coming from the OnlyFans events?

The catalogs are not identical. If you're porting an existing OnlyFans integration:

OnlyFans eventFansly equivalent
subscriptions.new / subscriptions.renewedfansly.subscriptions.new / fansly.subscriptions.renewed — the same split
subscriptions.expiredfansly.subscriptions.expired. Fansly pushes this one; OnlyFans has no expiry signal, so the OnlyFans event is derived and arrives within about 15 minutes rather than immediately
messages.ppv.unlockedfansly.media.purchased
users.online / users.offlineNot available. Fansly exposes no fan presence signal — poll lastSeenAt on Get User Details instead
chat_queue.updated / chat_queue.finishedNot available
accounts.reconnected, accounts.session_expiredFolded into fansly.accounts.connected
accounts.disconnectedfansly.accounts.disconnected
accounts.otp_code_requiredfansly.accounts.otp_code_required, for both the emailed and authenticator-app code
accounts.face_otp_requiredNot available. Fansly has no selfie or liveness step
(none)Fansly adds reactions, read receipts, wall post lifecycle, post comments, media likes, followers, payouts, story purchases, wallet balances, blocks, streams and profile changes

Accounts

fansly.accounts.connected

A Fansly account was connected or re-authenticated. Fires for all of your Fansly accounts.

Example payload
{
    "event": "fansly.accounts.connected",
    "account_id": "fansly_acct_123",
    "payload": {
        "status": "authenticated",
        "is_session_active": true,
        "last_authenticated_at": "2026-01-01T02:33:20+00:00",
        "last_error": null,
        "fansly_user": {
            "id": "100000000000000001",
            "username": "example_creator",
            "display_name": "Example Creator"
        }
    }
}

fansly.accounts.disconnected

A Fansly account was disconnected, either from the dashboard or with the Disconnect Account endpoint.

Every request for that fansly_acct_ ID from this point on returns a 404, so stop polling it when this arrives.

Reconnecting gives you a new fansly_acct_ ID

The disconnected fansly_acct_ ID is never reused. If the creator reconnects later, they come back as a new account, so key your own records on fansly_user.id — that snowflake is the creator's Fansly identity and we re-link the same one on reconnect.

Unlike the two events above, the payload carries no status, is_session_active or last_error: those describe a live session, and the account no longer has one.

Example payload
{
    "event": "fansly.accounts.disconnected",
    "account_id": "fansly_acct_123",
    "payload": {
        "account_id": "fansly_acct_123",
        "fansly_user": {
            "id": "100000000000000001",
            "username": "example_creator",
            "display_name": "Example Creator"
        },
        "disconnected_at": "2026-01-01T02:33:20+00:00"
    }
}

Not the same as a broken session

This event only fires when the account is deliberately disconnected. A login that breaks on its own gives you fansly.accounts.authentication_failed instead, and the account stays connected until you disconnect it.

fansly.accounts.authentication_failed

An account login attempt failed. Reconnect with our /authenticate endpoint or manually in our dashboard.

Example payload
{
    "event": "fansly.accounts.authentication_failed",
    "account_id": "fansly_acct_123",
    "payload": {
        "status": "auth-failed",
        "is_session_active": false,
        "last_authenticated_at": null,
        "last_error": "Fansly login failed: invalid credentials",
        "fansly_user": null
    }
}

fansly.accounts.otp_code_required

A connect or re-authenticate attempt stopped on Fansly's two-factor challenge: the creator has to give you a code before we can finish the login. Fires for both Start Authentication and Re-authenticate Account.

One event covers both code sources. twofa_type — and its string alias twofa_method — tells you where the creator gets the code:

  • 2 / "email" — a code Fansly emailed to the account's own address, masked in masked_email. This is what a login from a new IP gets.
  • 0 / "app" — a code from the creator's authenticator app. masked_email is null, because there is no destination to show.

You submit it the same way either way, with Submit 2FA. The challenge is only valid until expires_at (15 minutes from when it was raised); after that, start a fresh attempt to get a new code.

Example payload (emailed code)
{
    "event": "fansly.accounts.otp_code_required",
    "account_id": "fansly_acct_123",
    "payload": {
        "status": "challenge-email",
        "is_session_active": false,
        "last_authenticated_at": null,
        "last_error": null,
        "fansly_user": null,
        "twofa_type": 2,
        "twofa_method": "email",
        "masked_email": "e*****@e*****.com",
        "expires_at": "2026-01-01T02:48:20.000000Z"
    }
}

For the authenticator-app challenge, status is challenge-app and there is no masked destination:

Example payload (authenticator app)
{
    "event": "fansly.accounts.otp_code_required",
    "account_id": "fansly_acct_123",
    "payload": {
        "status": "challenge-app",
        "is_session_active": false,
        "last_authenticated_at": null,
        "last_error": null,
        "fansly_user": null,
        "twofa_type": 0,
        "twofa_method": "app",
        "masked_email": null,
        "expires_at": "2026-01-01T02:48:20.000000Z"
    }
}

The fansly_user block and last_authenticated_at are null while a first connection is still being set up, as above. On a re-authentication of an account you already know, both are filled in with the identity and the previous session's timestamp.

A rejected code does not re-send this event

If the wrong code is submitted, the challenge stays pending and you get no second event — Poll Authentication Status keeps returning needs_code: true, with the reason in last_error. Ask the creator for the code again and submit it. A new event fires only when a fresh attempt is started.

The account stays the source of truth

The same challenge is always readable from the account itself: needs_code, twofa_type and masked_email on Poll Authentication Status, and authentication_progress on List Accounts. Check there if your endpoint was down when the challenge was raised, rather than waiting for a redelivery.

fansly.accounts.alert

Fansly raised an alert against the connected account. metadata.reason says which — switch on it, because the rest of the block differs per reason.

Every alert we have observed so far belongs to the co-performer consent workflow:

  • consent_request_received — someone asked you to confirm you appear in their content. requester_account_id is who asked, request_id the request itself.
  • consent_sign_expired — a consent form you rely on has lapsed, which blocks the content it covers. co_performer_id and nickname identify the other performer.

Fansly ships metadata as a string

On the wire this field is a JSON-encoded string. We decode it for you, so metadata arrives as a real object. If it ever isn't decodable we forward it untouched, so guard with a type check before indexing into it.

Example payload
{
    "event": "fansly.accounts.alert",
    "account_id": "fansly_acct_123",
    "payload": {
        "id": "800000000000000008",
        "accountId": "100000000000000001",
        "type": 24001,
        "correlationId": "300000000000000003",
        "correlationGroupId": null,
        "lookups": [
            {
                "lookupId": "300000000000000003"
            }
        ],
        "metadata": {
            "reason": "consent_sign_expired",
            "co_performer_id": "300000000000000003",
            "nickname": "Example Co-performer",
            "feedback_note": ""
        }
    }
}

fansly.accounts.updated

The connected account's own record changed on Fansly — username, display name, email, or its flags bitfield. modelFlagChange marks a change to the account's model status.

version increments on every change, so use it to order events that arrive out of sequence.

Example payload
{
    "event": "fansly.accounts.updated",
    "account_id": "fansly_acct_123",
    "payload": {
        "id": "100000000000000001",
        "email": "creator@example.com",
        "username": "examplecreator",
        "displayName": "Example Creator",
        "flags": 18,
        "version": 14,
        "createdAt": 1767230000000,
        "modelFlagChange": false
    }
}

Messages & chats

fansly.messages.received

A message was received from a fan. Includes the resolved sender identity in senderData.

Example payload
{
    "event": "fansly.messages.received",
    "account_id": "fansly_acct_123",
    "payload": {
        "type": 1,
        "attachments": [],
        "content": "hey! loved your latest post",
        "groupId": "100000000000000001",
        "senderId": "200000000000000002",
        "inReplyTo": "",
        "interactions": [
            {
                "groupId": "100000000000000001",
                "userId": "300000000000000003",
                "readAt": 0,
                "deliveredAt": 0,
                "messageId": "400000000000000004"
            }
        ],
        "id": "400000000000000004",
        "createdAt": 1767230000.045,
        "embeds": [],
        "direction": "incoming",
        "senderData": {
            "available": true,
            "id": "200000000000000002",
            "username": "example_fan",
            "display_name": "Example Fan",
            "avatar": "https://cdn.fansly.example/avatars/200000000000000002.jpg"
        }
    }
}

fansly.messages.sent

A message was sent from one of your Fansly accounts, from our API, the Fansly app, or any other tool.

Example payload
{
    "event": "fansly.messages.sent",
    "account_id": "fansly_acct_123",
    "payload": {
        "type": 1,
        "attachments": [],
        "content": "thank you so much! 💕",
        "groupId": "100000000000000001",
        "senderId": "300000000000000003",
        "inReplyTo": "",
        "interactions": [
            {
                "groupId": "100000000000000001",
                "userId": "200000000000000002",
                "readAt": 0,
                "deliveredAt": 0,
                "messageId": "500000000000000005"
            }
        ],
        "id": "500000000000000005",
        "createdAt": 1767231000.421,
        "embeds": [],
        "direction": "outgoing"
    }
}

fansly.messages.deleted

A chat message was deleted. The payload carries the last known message body, so you can reconcile without a lookup.

Example payload
{
    "event": "fansly.messages.deleted",
    "account_id": "fansly_acct_123",
    "payload": {
        "id": "400000000000000004",
        "type": 1,
        "dataVersion": 1,
        "content": "hey! loved your latest post",
        "groupId": "100000000000000001",
        "senderId": "200000000000000002",
        "correlationId": "0",
        "inReplyTo": null,
        "inReplyToRoot": null,
        "createdAt": 1767230000,
        "attachments": [],
        "embeds": [],
        "interactions": [
            {
                "userId": "300000000000000003",
                "readAt": 0,
                "deliveredAt": 1767230004000
            }
        ],
        "likes": [],
        "deletedAt": 1767230009,
        "direction": "incoming"
    }
}

fansly.messages.read

A read receipt for a conversation. messageIds lists the messages marked as read by userId.

Example payload
{
    "event": "fansly.messages.read",
    "account_id": "fansly_acct_123",
    "payload": {
        "groupId": "100000000000000001",
        "messageIds": [
            "400000000000000004"
        ],
        "userId": "200000000000000002",
        "userReadReceiptsEnabled": true,
        "type": 1,
        "recipients": [
            {
                "userId": "300000000000000003",
                "readReceiptsEnabled": true
            },
            {
                "userId": "200000000000000002",
                "readReceiptsEnabled": true
            }
        ]
    }
}

fansly.messages.reaction_added

A fan reacted to a chat message. type is the emoji reaction id.

Example payload
{
    "event": "fansly.messages.reaction_added",
    "account_id": "fansly_acct_123",
    "payload": {
        "accountId": "200000000000000002",
        "messageId": "400000000000000004",
        "type": 5,
        "groupId": "100000000000000001",
        "id": "410000000000000041"
    }
}

fansly.messages.reaction_removed

A reaction was removed from a chat message.

Example payload
{
    "event": "fansly.messages.reaction_removed",
    "account_id": "fansly_acct_123",
    "payload": {
        "accountId": "200000000000000002",
        "messageId": "400000000000000004",
        "type": 5,
        "groupId": "100000000000000001",
        "id": "410000000000000042"
    }
}

fansly.chats.visibility_updated

A conversation was hidden or unhidden. userSettings.hidden is the resulting state: 1 hidden, 0 visible.

High volume

This fires on every hide and unhide across your inbox. Only subscribe if you mirror conversation state; otherwise ignore it.

Example payload
{
    "event": "fansly.chats.visibility_updated",
    "account_id": "fansly_acct_123",
    "payload": {
        "groupId": "100000000000000001",
        "userId": "200000000000000002",
        "userSettings": {
            "hidden": 0
        }
    }
}

Posts

fansly.posts.created

You published a new wall post.

One event per post, not per wall

Fansly emits a separate frame for every wall a post lands on. We collapse them, so a post cross-posted to several walls arrives once, carrying the first wall we saw it on. Adding an existing post to a further wall more than 24 hours later does deliver again.

Example payload
{
    "event": "fansly.posts.created",
    "account_id": "fansly_acct_123",
    "payload": {
        "postId": "500000000000000005",
        "wallId": "200000000000000010",
        "wall": {
            "id": "200000000000000010",
            "accountId": "100000000000000001",
            "pos": 0,
            "name": "Posts",
            "description": "",
            "private": 0,
            "metadata": ""
        },
        "post": {
            "id": "500000000000000005",
            "accountId": "100000000000000001",
            "content": "New set just dropped 🔥 #newpost",
            "fypFlags": 0,
            "inReplyTo": null,
            "inReplyToRoot": null,
            "createdAt": 1767230000,
            "expiresAt": null,
            "attachments": [
                {
                    "postId": "500000000000000005",
                    "pos": 0,
                    "contentType": 1,
                    "contentId": "600000000000000006",
                    "deletedAt": null
                }
            ]
        },
        "metadata": {
            "recentPost": true
        }
    }
}

fansly.posts.updated

You edited a wall post.

Example payload
{
    "event": "fansly.posts.updated",
    "account_id": "fansly_acct_123",
    "payload": {
        "id": "500000000000000005",
        "accountId": "100000000000000001",
        "content": "New set just dropped 🔥 (now with more pics) #newpost",
        "fypFlags": 0,
        "inReplyTo": null,
        "inReplyToRoot": null,
        "createdAt": 1767230000,
        "expiresAt": 0,
        "attachments": [
            {
                "contentType": 1,
                "contentId": "600000000000000006"
            }
        ],
        "likeCount": 12,
        "postReplyPermissionFlags": [],
        "wallIds": [
            "200000000000000010"
        ]
    }
}

fansly.posts.deleted

A wall post was deleted. Like fansly.posts.created, a post that was on several walls arrives once rather than once per wall.

For a post that was removed because it hit its own expiry, fansly.posts.expired fires alongside this one and carries the full post body.

Example payload
{
    "event": "fansly.posts.deleted",
    "account_id": "fansly_acct_123",
    "payload": {
        "wallId": "200000000000000010",
        "postId": "500000000000000005",
        "wall": {
            "id": "200000000000000010",
            "accountId": "100000000000000001",
            "pos": 0,
            "name": "Posts",
            "description": "",
            "private": 1,
            "metadata": ""
        }
    }
}

fansly.posts.pinned

A wall post was pinned.

Example payload
{
    "event": "fansly.posts.pinned",
    "account_id": "fansly_acct_123",
    "payload": {
        "postId": "500000000000000005",
        "accountId": "100000000000000001",
        "pos": 0,
        "wallId": "200000000000000010"
    }
}

fansly.posts.expired

A post was removed because it reached its own expiresAt. Where fansly.posts.deleted only identifies the post and the wall, this carries the whole post — content, attachments and all — so you can archive it as it goes.

Both events fire for an expiring post. Subscribe to whichever suits you, or to both if you want the body and the wall.

Example payload
{
    "event": "fansly.posts.expired",
    "account_id": "fansly_acct_123",
    "payload": {
        "id": "500000000000000005",
        "accountId": "100000000000000001",
        "content": "Only up for 3 days 👀 #example",
        "fypFlags": 2,
        "inReplyTo": null,
        "inReplyToRoot": null,
        "createdAt": 1767230000,
        "expiresAt": 1767489200000,
        "attachments": [
            {
                "postId": "500000000000000005",
                "pos": 0,
                "contentType": 1,
                "contentId": "600000000000000006",
                "deletedAt": null
            }
        ],
        "delitionType": 1
    }
}

createdAt and expiresAt use different units

createdAt is epoch seconds here, while expiresAt is epoch milliseconds — Fansly's own inconsistency, forwarded as-is. Check the magnitude before parsing, as noted in the payload conventions.

fansly.posts.commented

Someone commented on, replied to, or quoted one of your posts. kind is reply for a comment or a reply to one, and quote when your post was quoted by another post.

A comment on Fansly is itself a post, and the notification behind this event carries only IDs — so we fetch the comment and its author for you and deliver them under comment.

Check comment.available first

Enrichment is best-effort: if the lookup fails, or the comment is already gone by the time we fetch it, the event is still delivered with comment.available: false and an error block instead of the body. Always branch on available before reading content or author.

comment.in_reply_to is what was replied to directly and comment.in_reply_to_root is the post the thread hangs off. They match for a top-level comment and differ when a fan replies to another comment.

Example payload
{
    "event": "fansly.posts.commented",
    "account_id": "fansly_acct_123",
    "payload": {
        "id": "800000000000000008",
        "accountId": "100000000000000001",
        "type": 1004,
        "correlationId": "500000000000000006",
        "correlationGroupId": "500000000000000005",
        "kind": "reply",
        "post_id": "500000000000000005",
        "comment": {
            "available": true,
            "id": "500000000000000006",
            "content": "love this 😍",
            "created_at": 1767230000,
            "in_reply_to": "500000000000000005",
            "in_reply_to_root": "500000000000000005",
            "quoted_post_id": null,
            "attachments": [],
            "author": {
                "available": true,
                "id": "200000000000000002",
                "username": "example_fan",
                "display_name": "Example Fan",
                "avatar": "https://cdn.fansly.example/a.jpg"
            }
        }
    }
}

fansly.posts.liked

A fan liked one of your posts. correlationId is the post, correlationGroupId is the fan who liked it.

Example payload
{
    "event": "fansly.posts.liked",
    "account_id": "fansly_acct_123",
    "payload": {
        "id": "920000000000000021",
        "type": 1002,
        "accountId": "100000000000000001",
        "correlationId": "700000000000000070",
        "correlationGroupId": "200000000000000002",
        "lookups": [
            {
                "lookupId": "930000000000000030"
            }
        ]
    }
}

Media

fansly.media.purchased

A fan purchased a media item. This is the Fansly equivalent of an unlocked PPV. Bundles fire one event per item, so a single checkout can produce several events. orderMetadata.accountMediaPrice is in thousandths of a dollar.

Example payload
{
    "event": "fansly.media.purchased",
    "account_id": "fansly_acct_123",
    "payload": {
        "orderId": "940000000000000040",
        "accountMediaId": "800000000000000080",
        "correlationAccountId": "100000000000000001",
        "accountId": "200000000000000002",
        "type": 1,
        "orderMetadata": {
            "accountMediaPrice": 15000
        }
    }
}

fansly.media.liked

A fan liked a media item. correlationId is the media, correlationGroupId is the fan.

High volume

Popular accounts can receive these continuously. Only subscribe if you actually process them, and make sure your endpoint can keep up.

Example payload
{
    "event": "fansly.media.liked",
    "account_id": "fansly_acct_123",
    "payload": {
        "id": "920000000000000022",
        "type": 2002,
        "accountId": "100000000000000001",
        "correlationId": "800000000000000080",
        "correlationGroupId": "200000000000000002",
        "lookups": [
            {
                "lookupId": "930000000000000031"
            }
        ],
        "metadata": "{\"likeId\":\"930000000000000031\",\"accountMediaAccess\":true}"
    }
}

Subscriptions

fansly.subscriptions.new

A fan subscribed to you for the first time. Rebills of an existing subscription arrive as fansly.subscriptions.renewed instead.

price and renewPrice are in thousandths of a dollar, and the timestamps are epoch milliseconds.

This changed in August 2026

This event used to fire for renewals too. If your integration predates that change and counts new subscribers from this event, it was over-counting, and it now needs to consume fansly.subscriptions.renewed as well to see the same volume.

A first subscription is recognisable on its own terms: subscriptionStreak and subscriptionTotalDays are both 0, and createdAt is the moment the event fires.

Example payload
{
    "event": "fansly.subscriptions.new",
    "account_id": "fansly_acct_123",
    "payload": {
        "id": "500000000000000005",
        "historyId": "510000000000000005",
        "accountId": "100000000000000001",
        "subscriberId": "200000000000000002",
        "subscriptionTierId": "600000000000000006",
        "subscriptionTierName": "Example Tier",
        "subscriptionTierColor": "#46A7F8",
        "planId": "610000000000000006",
        "promoId": "0",
        "giftCodeId": null,
        "paymentMethodId": "0",
        "status": 3,
        "price": 9990,
        "renewPrice": 9990,
        "renewCorrelationId": "510000000000000005",
        "autoRenew": 1,
        "version": 2,
        "billingCycle": 30,
        "duration": 30,
        "renewDate": 1767230000000,
        "createdAt": 1767230000000,
        "endsAt": 1769908400000,
        "promoDuration": null,
        "promoStartsAt": null,
        "promoEndsAt": null,
        "updatedAt": 1767230000998,
        "subscriptionStreak": 0,
        "subscriptionTotalDays": 0
    }
}

fansly.subscriptions.renewed

An existing subscription rebilled. subscriptionStreak counts the consecutive billing cycles, so 5 is the fan's fifth month, and subscriptionTotalDays is how long they have been subscribed in total.

The payload is the same shape as fansly.subscriptions.new, with historyId freshly minted for this cycle, version incremented, and endsAt pushed out by another billingCycle.

Example payload
{
    "event": "fansly.subscriptions.renewed",
    "account_id": "fansly_acct_123",
    "payload": {
        "id": "500000000000000005",
        "historyId": "520000000000000005",
        "accountId": "100000000000000001",
        "subscriberId": "200000000000000002",
        "subscriptionTierId": "600000000000000006",
        "subscriptionTierName": "Example Tier",
        "subscriptionTierColor": "#FF69B4",
        "planId": "610000000000000006",
        "promoId": null,
        "giftCodeId": null,
        "paymentMethodId": "0",
        "status": 3,
        "price": 5550,
        "renewPrice": 5550,
        "renewCorrelationId": "510000000000000005",
        "autoRenew": 1,
        "version": 21,
        "billingCycle": 30,
        "duration": 30,
        "renewDate": 1754390000000,
        "createdAt": 1754390000000,
        "endsAt": 1770290000000,
        "promoDuration": null,
        "promoStartsAt": null,
        "promoEndsAt": null,
        "updatedAt": 1767611600000,
        "subscriptionStreak": 5,
        "subscriptionTotalDays": 150
    }
}

A brief lapse before each rebill is normal

Fansly marks the subscription expired for a moment before the renewal lands. We don't deliver that intermediate state, so a rebill normally reaches you as this event alone rather than as an expiry followed by a new subscription.

fansly.subscriptions.expired

A fan's subscription expired. correlationId is the subscription, correlationGroupId is the fan.

Example payload
{
    "event": "fansly.subscriptions.expired",
    "account_id": "fansly_acct_123",
    "payload": {
        "id": "920000000000000020",
        "type": 15007,
        "accountId": "100000000000000001",
        "correlationId": "500000000000000005",
        "correlationGroupId": "200000000000000002"
    }
}

Followers

fansly.followers.new

A fan followed your account.

Example payload
{
    "event": "fansly.followers.new",
    "account_id": "fansly_acct_123",
    "payload": {
        "id": "910000000000000010",
        "followerId": "200000000000000002",
        "accountId": "100000000000000001",
        "accountSortOrder": 1
    }
}

fansly.followers.removed

A fan unfollowed your account.

createdAt is the date of the original follow, not the unfollow. Use your own receive time if you need to know when the unfollow happened.

Example payload
{
    "event": "fansly.followers.removed",
    "account_id": "fansly_acct_123",
    "payload": {
        "id": "910000000000000010",
        "followerId": "200000000000000002",
        "accountId": "100000000000000001",
        "createdAt": 1764547200000
    }
}

Fans

fansly.fans.ignore_updated

You blocked, muted, or cleared either for a Fansly user — from our Block User endpoint, the Fansly app, or anywhere else. ignoredId is the user it was applied to.

state is the decoded result of ignoreFlags, so you don't have to know the numbers:

ignoreFlagsstateMeaning
0noneUnblocked or unmuted
1mutedMuted
2blockedBlocked

A cleared flag doesn't say what it undid

One frame carries only the resulting state, with no record of what came before, so state: "none" means "no longer blocked or muted" without distinguishing the two. Track the previous state on your side if you need to tell an unblock from an unmute.

Example payload
{
    "event": "fansly.fans.ignore_updated",
    "account_id": "fansly_acct_123",
    "payload": {
        "accountId": "100000000000000001",
        "ignoredId": "200000000000000002",
        "ignoreFlags": 2,
        "state": "blocked"
    }
}

Profiles

fansly.profiles.updated

Your public profile changed — avatar, banner, bio, location, socials or badges. avatarId and bannerId are media IDs rather than URLs; read the resolved avatar from Get User Details if you need something displayable.

Example payload
{
    "event": "fansly.profiles.updated",
    "account_id": "fansly_acct_123",
    "payload": {
        "accountId": "100000000000000001",
        "avatarId": "700000000000000007",
        "bannerId": "700000000000000008",
        "about": "Example bio 🌸",
        "location": "Example City",
        "socials": [],
        "badges": []
    }
}

Streams

fansly.streams.updated

A live stream changed, or its access permissions did. Both arrive as this one event.

status is forwarded raw

We pass Fansly's status through without interpreting it, because we have not yet seen enough of its values in production to map them to started/ended reliably. Compare version between events to order them, and treat status as opaque unless you have verified a value yourself.

Example payload
{
    "event": "fansly.streams.updated",
    "account_id": "fansly_acct_123",
    "payload": {
        "id": "400000000000000005",
        "channelId": "400000000000000004",
        "accountId": "100000000000000001",
        "title": "",
        "status": 1,
        "viewerCount": 0,
        "version": 1938,
        "createdAt": 1767230000000,
        "updatedAt": null,
        "lastFetchedAt": 1767230000829
    }
}

Wallets

fansly.wallets.updated

Your earnings wallet balance changed. Fires on every earning and every payout, so it is a running mirror of your balance rather than a record of what caused the change — pair it with fansly.transactions.new for that.

High volume

A busy account changes balance constantly. Only subscribe if you display or reconcile a live balance.

Both balance and balance64 are forwarded exactly as Fansly sends them, in thousandths of a dollar. They do not always agree; walletVersion increments on every change and orders the events.

Example payload
{
    "event": "fansly.wallets.updated",
    "account_id": "fansly_acct_123",
    "payload": {
        "id": "900000000000000009",
        "balance": 4548002,
        "balance64": 4530402,
        "accountId": "100000000000000001",
        "type": 2,
        "flags": 0,
        "walletVersion": 2372,
        "updatedAt": 1767230000112
    }
}

Payouts

fansly.payouts.created

A payout was requested. amount is in thousandths of a dollar, so 5000000 is $5,000.00.

Example payload
{
    "event": "fansly.payouts.created",
    "account_id": "fansly_acct_123",
    "payload": {
        "id": "950000000000000050",
        "accountId": "100000000000000001",
        "amount": 5000000,
        "payoutMethodId": "960000000000000060",
        "status": 1,
        "version": 0
    }
}

fansly.payouts.updated

A payout request changed status. Compare version to detect ordering if events arrive out of sequence.

Example payload
{
    "event": "fansly.payouts.updated",
    "account_id": "fansly_acct_123",
    "payload": {
        "id": "950000000000000050",
        "accountId": "100000000000000001",
        "amount": 5000000,
        "payoutMethodId": "960000000000000060",
        "status": 2,
        "version": 1
    }
}

fansly.payouts.debited

Money actually left your earnings wallet for a payout. Where the two events above track the payout request and its status, this is the wallet movement itself.

Deliberately kept out of fansly.transactions.new

A payout is an outflow, so including it in the earnings feed would let any integration that sums fansly.transactions.new count a withdrawal as income. It is delivered here and only here.

amount is in thousandths of a dollar. originWalletId is the wallet debited, and destinationWalletId is null because the money leaves Fansly entirely.

Example payload
{
    "event": "fansly.payouts.debited",
    "account_id": "fansly_acct_123",
    "payload": {
        "id": "800000000000000008",
        "type": 16012,
        "originWalletId": "900000000000000009",
        "destinationWalletId": null,
        "status": 2,
        "amount": 15200000,
        "createdAt": 1767230000000
    }
}

Transactions & Tips

fansly.transactions.new

A new earning: a subscription, tip, PPV purchase, and so on. Payouts are excluded — they arrive as fansly.payouts.debited instead.

amount is the gross amount, destinationTax the platform cut, and destinationAmount what you actually receive. All three are in thousandths of a dollar.

Example payload
{
    "event": "fansly.transactions.new",
    "account_id": "fansly_acct_123",
    "payload": {
        "id": "700000000000000007",
        "type": 2110,
        "correlationId": "800000000000000008",
        "walletId": "900000000000000009",
        "destinationWalletId": "900000000000000009",
        "accountId": "200000000000000002",
        "amount": 15000,
        "destinationTax": 2000,
        "destinationAmount": 12000,
        "senderId": "200000000000000002",
        "receiverId": "100000000000000001",
        "status": 1,
        "createdAt": 1767230000000,
        "updatedAt": 1767230000000
    }
}

fansly.tips.received

You received a tip from a fan. This is the same transaction shape as above with a type in the tip range, so a tip that you subscribe to both events for will arrive twice: once as fansly.tips.received and once as fansly.transactions.new.

Example payload
{
    "event": "fansly.tips.received",
    "account_id": "fansly_acct_123",
    "payload": {
        "id": "700000000000000007",
        "type": 7101,
        "correlationId": "800000000000000008",
        "walletId": "900000000000000009",
        "destinationWalletId": "900000000000000009",
        "accountId": "200000000000000002",
        "amount": 5000,
        "destinationTax": 2000,
        "destinationAmount": 4000,
        "senderId": "200000000000000002",
        "receiverId": "100000000000000001",
        "status": 1,
        "createdAt": 1767230000000,
        "updatedAt": 1767230000000
    }
}

Stories

fansly.stories.purchased

A fan purchased one of your paid stories. This event is keyed on transactionId. There is no orderId like on fansly.media.purchased.

Example payload
{
    "event": "fansly.stories.purchased",
    "account_id": "fansly_acct_123",
    "payload": {
        "accountId": "200000000000000002",
        "storyId": "810000000000000081",
        "transactionId": "820000000000000082",
        "price": 15000,
        "type": 1,
        "correlationAccountId": "100000000000000001"
    }
}

Users

fansly.users.typing

A fan is typing in a conversation.

High volume

Typing indicators fire repeatedly for a single message being composed. Debounce on your side, and don't subscribe unless you're rendering a live inbox.

Example payload
{
    "event": "fansly.users.typing",
    "account_id": "fansly_acct_123",
    "payload": {
        "accountId": "200000000000000002",
        "groupId": "100000000000000001",
        "lastAnnounce": 1767229990129
    }
}

No presence events

Fansly does not expose fan online/offline presence, so there is no equivalent of the OnlyFans users.online and users.offline events.

Poll the lastSeenAt field on Get User Details instead — see the FAQ for the caveats.

On this page