Available OnlyFans webhook events
A list of all available OnlyFans webhook events that you can subscribe to.
Looking for a specific event?
Please reach out to us, we can add it!
Looking for Fansly? See the available Fansly webhook events.
Accounts
accounts.connected
A new OnlyFans account was connected.
Please note that payload.client_reference_id can be null - it's only
included if you provided us with client_reference_id while Creating Client
Session
{
"event": "accounts.connected",
"account_id": "acct_123",
"payload": {
"client_reference_id": "my_crm_model_12345", // Optional - Value you provided while creating Client Session - otherwise null
"state": "authenticated",
"progress": "started",
"latestAuthAttempt": {
"started_at": "2025-05-13T20:26:56.000000Z",
"completed_at": null,
"success": false,
"error_message": null,
"needs_otp": false,
"needs_face_otp": false,
"otp_phone_ending": null
}
}
}accounts.reconnected
An OnlyFans account was reconnected.
{
"event": "accounts.reconnected",
"account_id": "acct_123",
"payload": {
"state": "authenticated",
"progress": "started",
"latestAuthAttempt": {
"started_at": "2025-05-13T20:26:56.000000Z",
"completed_at": null,
"success": false,
"error_message": null,
"needs_otp": false,
"needs_face_otp": false,
"otp_phone_ending": null
}
}
}accounts.disconnected
An OnlyFans account was disconnected, either from the dashboard or with the Disconnect Account endpoint.
Every request for that acct_ ID from this point on returns a 404 with account_not_found, so stop polling it when this arrives.
Reconnecting gives you a new acct_ ID
The disconnected acct_ ID is never reused. If the creator reconnects later,
they come back as a new account, so key your own records on onlyfans_id (the
OnlyFans user ID, stable across reconnects) rather than on the acct_ ID.
{
"event": "accounts.disconnected",
"account_id": "acct_123",
"payload": {
"account_id": "acct_123",
"onlyfans_id": "123456789",
"onlyfans_username": "janedoe",
"disconnected_at": "2025-05-13T20:26:56+00:00"
}
}Not the same as a broken session
This event only fires when the account is deliberately disconnected. A
connection that breaks on its own gives you
accounts.session_expired or
accounts.authentication_failed instead, and
the account stays connected and usable in both of those cases.
accounts.session_expired
OnlyFans account connection has expired, but we automatically re-connected it using our system - you don't need to do anything.
{
"event": "accounts.session_expired",
"account_id": "acct_123",
"payload": {
"state": "authenticated",
"progress": "started",
"latestAuthAttempt": {
"started_at": "2025-05-13T20:26:56.000000Z",
"completed_at": null,
"success": false,
"error_message": null,
"needs_otp": false,
"needs_face_otp": false,
"otp_phone_ending": null
}
}
}accounts.authentication_failed
OnlyFans account connection has expired, and we couldn't automatically re-connect it using our system. You need to reconnect with our /authenticate endpoint or manually in our dashboard.
{
"event": "accounts.authentication_failed",
"account_id": "acct_123",
"payload": {
"state": "authenticated",
"progress": "started",
"latestAuthAttempt": {
"started_at": "2025-05-13T20:26:56.000000Z",
"completed_at": null,
"success": false,
"error_message": null,
"needs_otp": false,
"needs_face_otp": false,
"otp_phone_ending": null
}
}
}accounts.otp_code_required
A two-factor authentication code is required to connect one of your OnlyFans accounts.
{
"event": "accounts.otp_code_required",
"account_id": "acct_123",
"payload": {
"state": "authenticated",
"progress": "started",
"latestAuthAttempt": {
"started_at": "2025-05-13T20:26:56.000000Z",
"completed_at": null,
"success": false,
"error_message": null,
"needs_otp": false,
"needs_face_otp": false,
"otp_phone_ending": null
}
}
}accounts.face_otp_required
Face verification is required to connect one of your OnlyFans accounts.
{
"event": "accounts.face_otp_required",
"account_id": "acct_123",
"payload": {
"state": "authenticated",
"progress": "started",
"face_otp_verification_url": "https://id.onlyfans.com/s?123",
"latestAuthAttempt": {
"started_at": "2025-05-13T20:26:56.000000Z",
"completed_at": null,
"success": false,
"error_message": null,
"needs_otp": false,
"needs_face_otp": false,
"otp_phone_ending": null
}
}
}Transactions
transactions.new
A new transaction has been received from a fan (eg. a subscription, a tip, a post, etc.).
Please note that this event may be delayed by up to 3 minutes.
Supported payload.type values | Description |
|---|---|
message | A message has been sent to you |
post | Payment for a post |
tip | Payment for a tip |
new_subscription | Payment for a new subscription |
recurring_subscription | Payment for a recurring subscription |
stream | Payment for a stream |
other | Another type of transaction |
{
"event": "transactions.new",
"account_id": "acct_123",
"payload": {
"id": "abc123",
"created_at": "2026-01-01 00:00:00",
"type": "message",
"tips_source": null,
"amount": 10,
"vat_amount": 0,
"tax_amount": 0,
"net_amount": 8,
"fee_amount": 2,
"currency": "USD",
"description": "Payment for message from \"<a href=\"https://onlyfans.com/johndoe\">John Doe</a>\"",
"status": "loading",
"fan": {
"id": "123",
"username": "johndoe",
"name": "John Doe",
"display_name": "",
"avatar": null
},
"fanData": {
"available": true,
"last_updated_at": "2026-01-01T00:00:00.000000Z",
"spending": {
"total": 6.6,
"messages": 10,
"subscribes": 2.6,
"posts": 0,
"tips": 4,
"streams": 0
},
"previous_reply_at": null
}
}
}Messages
messages.received
New message received from a fan.
{
"event": "messages.received",
"account_id": "acct_123",
"payload": {
"responseType": "message",
"text": "<p>Text</p>",
"giphyId": null,
"lockedText": false,
"isFree": true,
"price": 0,
"isMediaReady": true,
"mediaCount": 0,
"media": [],
"previews": [],
"isTip": false,
"isReportedByMe": false,
"isCouplePeopleMedia": false,
"queueId": 123,
"isMarkdownDisabled": true,
"fanData": {
"available": true,
"last_updated_at": "2025-10-23 00:52:34",
"spending": {
"total": 50,
"messages": 30,
"subscribes": 10,
"posts": 0,
"tips": 20,
"streams": 0
}
},
"fromUser": {
"view": "s",
"avatar": "https://example.com",
"avatarThumbs": {
"c50": "https://example.com",
"c144": "https://example.com"
},
"header": null,
"headerSize": null,
"headerThumbs": null,
"id": 123,
"name": "Name",
"username": "username",
"canLookStory": true,
"canCommentStory": true,
"hasNotViewedStory": false,
"isVerified": false,
"canPayInternal": true,
"hasScheduledStream": false,
"hasStream": false,
"hasStories": false,
"tipsEnabled": false,
"tipsTextEnabled": true,
"tipsMin": 5,
"tipsMinInternal": 1,
"tipsMax": 200,
"canEarn": false,
"canAddSubscriber": false,
"subscribePrice": 0,
"displayName": "",
"notice": "",
"isRestricted": false,
"canRestrict": true,
"subscribedBy": true,
"subscribedByExpire": false,
"subscribedByExpireDate": "2034-12-22T00:38:29+00:00",
"subscribedByAutoprolong": true,
"subscribedIsExpiredNow": false,
"currentSubscribePrice": 0,
"subscribedOn": true,
"subscribedOnExpiredNow": false,
"subscribedOnDuration": "4 months",
"listsStates": [
{
"id": "fans",
"type": "fans",
"name": "Fans",
"hasUser": true,
"canAddUser": false,
"cannotAddUserReason": "ALREADY_EXISTS"
},
{
"id": "following",
"type": "following",
"name": "Following",
"hasUser": true,
"canAddUser": false,
"cannotAddUserReason": "ALREADY_EXISTS"
},
{
"id": "recent",
"type": "recent",
"name": "Recent (last 24 hours)",
"hasUser": false,
"canAddUser": false,
"cannotAddUserReason": "SYSTEM_LIST"
},
{
"id": "muted",
"type": "muted",
"name": "Muted",
"hasUser": false,
"canAddUser": true,
"cannotAddUserReason": null
},
{
"id": "tagged",
"type": "tagged",
"name": "Tagged",
"hasUser": false,
"canAddUser": false,
"cannotAddUserReason": "SYSTEM_LIST"
},
{
"id": "friends",
"type": "friends",
"name": "Friends",
"hasUser": false,
"canAddUser": false,
"cannotAddUserReason": "USER_NOT_PERFORMER"
}
],
"showMediaCount": true,
"lastSeen": "2025-05-16T00:26:49+00:00",
"canReport": true
},
"isFromQueue": false,
"id": 123,
"isOpened": false,
"isNew": true,
"createdAt": "2025-05-16T00:27:25+00:00",
"changedAt": "2025-05-16T00:27:25+00:00",
"cancelSeconds": 120,
"isLiked": false,
"canPurchase": false,
"canPurchaseReason": "free",
"canReport": true,
"canBePinned": true,
"isPinned": false,
"replyToMessage": {
"responseType": "message",
"text": "<p>Text</p>",
"giphyId": null,
"lockedText": false,
"isFree": true,
"price": 0,
"isMediaReady": true,
"mediaCount": 0,
"media": [],
"previews": [],
"isTip": false,
"isReportedByMe": false,
"isCouplePeopleMedia": false,
"queueId": 123,
"isMarkdownDisabled": true,
"releaseForms": [],
"fromUser": {
"view": "s",
"avatar": "https://example.com",
"avatarThumbs": {
"c50": "https://example.com",
"c144": "https://example.com"
},
"header": "https://example.com",
"headerSize": {
"width": 4800,
"height": 1440
},
"headerThumbs": {
"w480": "https://example.com",
"w760": "https://example.com"
},
"id": 123,
"name": "Name",
"username": "username",
"canLookStory": true,
"canCommentStory": true,
"hasNotViewedStory": false,
"isVerified": true,
"canPayInternal": true,
"hasScheduledStream": false,
"hasStream": false,
"hasStories": true,
"tipsEnabled": true,
"tipsTextEnabled": true,
"tipsMin": 5,
"tipsMinInternal": 1,
"tipsMax": 200,
"canEarn": true,
"canAddSubscriber": false,
"subscribePrice": 0,
"hasStripe": false,
"isStripeExist": false,
"subscriptionBundles": [],
"canSendChatToAll": true,
"creditsMin": 10,
"creditsMax": 200,
"isPaywallPassed": true,
"creditsMinAlternatives": 5,
"canCreateLists": true,
"showMediaCount": true,
"lastSeen": "2025-05-16T00:24:06+00:00",
"canReport": false,
"subscribedOn": null
},
"isFromQueue": true,
"canUnsendQueue": true,
"unsendSecondsQueue": 1000000,
"id": 123,
"isOpened": false,
"isNew": false,
"createdAt": "2025-05-15T22:53:57+00:00",
"changedAt": "2025-05-15T22:53:57+00:00",
"cancelSeconds": 80792,
"isLiked": false,
"canPurchase": false,
"canPurchaseReason": "free",
"canReport": false,
"canBePinned": true,
"isPinned": false
}
}
}messages.sent
A new message has been sent from one of your OnlyFans accounts to a fan.
{
"event": "messages.sent",
"account_id": "acct_123",
"payload": {
"responseType": "message",
"text": "<p>Text</p>",
"giphyId": null,
"lockedText": false,
"isFree": true,
"price": 0,
"isMediaReady": true,
"mediaCount": 1,
"media": [
{
"id": 123,
"type": "photo",
"convertedToVideo": false,
"canView": true,
"hasError": false,
"createdAt": "2025-04-05T06:19:31+00:00",
"isReady": true,
"files": {
"full": {
"url": "https://example.com",
"width": 3840,
"height": 5215,
"size": 0,
"sources": []
},
"thumb": {
"url": "https://example.com",
"width": 300,
"height": 300,
"size": 0
},
"preview": {
"url": "https://example.com",
"width": 960,
"height": 1304,
"size": 0
},
"squarePreview": {
"url": "https://example.com",
"width": 960,
"height": 960,
"size": 0
}
},
"duration": 0,
"releaseForms": [],
"hasCustomPreview": false,
"videoSources": {
"240": null,
"720": null
}
}
],
"previews": [],
"isTip": false,
"isReportedByMe": false,
"isCouplePeopleMedia": false,
"queueId": 123,
"isMarkdownDisabled": true,
"releaseForms": [],
"isFromQueue": false,
"id": 123,
"isOpened": false,
"isNew": true,
"createdAt": "2025-05-15T23:37:24+00:00",
"changedAt": "2025-05-15T23:37:24+00:00",
"cancelSeconds": 86400,
"isLiked": false,
"canPurchase": false,
"canPurchaseReason": "free",
"canReport": false,
"canBePinned": true,
"isPinned": false,
"toUser": {
"view": "s",
"avatar": null,
"avatarThumbs": null,
"header": null,
"headerSize": null,
"headerThumbs": null,
"id": 123,
"name": "Name",
"username": "username",
"canLookStory": false,
"canCommentStory": false,
"hasNotViewedStory": false,
"isVerified": false,
"canPayInternal": true,
"hasScheduledStream": false,
"hasStream": false,
"hasStories": false,
"tipsEnabled": false,
"tipsTextEnabled": true,
"tipsMin": 5,
"tipsMinInternal": 1,
"tipsMax": 200,
"canEarn": false,
"canAddSubscriber": true,
"subscribePrice": 0,
"displayName": "",
"notice": "",
"isRestricted": false,
"canRestrict": true,
"subscribedBy": false,
"subscribedByExpire": null,
"subscribedByExpireDate": null,
"subscribedByAutoprolong": null,
"subscribedIsExpiredNow": null,
"currentSubscribePrice": null,
"subscribedOn": true,
"subscribedOnExpiredNow": false,
"subscribedOnDuration": "",
"listsStates": [
{
"id": "fans",
"type": "fans",
"name": "Fans",
"hasUser": true,
"canAddUser": false,
"cannotAddUserReason": "ALREADY_EXISTS"
},
{
"id": "following",
"type": "following",
"name": "Following",
"hasUser": false,
"canAddUser": false,
"cannotAddUserReason": "SYSTEM_LIST"
},
{
"id": "recent",
"type": "recent",
"name": "Recent (last 24 hours)",
"hasUser": true,
"canAddUser": false,
"cannotAddUserReason": "ALREADY_EXISTS"
},
{
"id": "muted",
"type": "muted",
"name": "Muted",
"hasUser": false,
"canAddUser": true,
"cannotAddUserReason": null
},
{
"id": "tagged",
"type": "tagged",
"name": "Tagged",
"hasUser": false,
"canAddUser": false,
"cannotAddUserReason": "SYSTEM_LIST"
},
{
"id": "friends",
"type": "friends",
"name": "Friends",
"hasUser": false,
"canAddUser": false,
"cannotAddUserReason": "USER_NOT_PERFORMER"
}
],
"showMediaCount": true,
"lastSeen": "2025-05-15T23:37:03+00:00",
"canReport": false
}
}
}messages.ppv.unlocked
A PPV message that you sent has been purchased by a fan.
{
"event": "messages.ppv.unlocked",
"account_id": "acct_123",
"payload": {
"id": "123",
"type": "paided_message",
"createdAt": "2025-05-05T22:18:00+00:00",
"text": "has purchased your <a href='https://onlyfans.com/my/chats/chat/123?firstId=123'>message</a> for $5.00!",
"replacePairs": {
"{NAME}": "Name",
"{MESSAGE_LINK}": "<a href='https://onlyfans.com/my/chats/chat/123?firstId=123'>message</a>",
"{AMOUNT}": "$5.00"
},
"subType": "subscriber_pay_for_chat_message",
"user_id": "123",
"isRead": false,
"canGoToProfile": true,
"fanData": {
"available": true,
"last_updated_at": "2026-01-02 17:12:00",
"spending": {
"total": 6.6,
"messages": 4,
"subscribes": 2.6,
"posts": 0,
"tips": 4,
"streams": 0
}
},
"user": {
"avatar": null,
"avatarThumbs": null,
"header": null,
"headerSize": null,
"headerThumbs": null,
"id": 123,
"name": "Name",
"username": "username",
"canLookStory": false,
"canCommentStory": false,
"hasNotViewedStory": false,
"isVerified": false,
"canPayInternal": false,
"hasScheduledStream": false,
"hasStream": false,
"hasStories": false,
"tipsEnabled": false,
"tipsTextEnabled": true,
"tipsMin": 5,
"tipsMinInternal": 1,
"tipsMax": 200,
"canEarn": false,
"canAddSubscriber": false,
"subscribePrice": 0,
"subscribedBy": false,
"subscribedOn": false,
"canReceiveChatMessage": false,
"hideChat": false,
"lastSeen": "2025-05-05T22:17:29+00:00",
"isPerformer": false,
"isRealPerformer": false,
"view": "mn",
"canReport": true,
"subscribedByData": null,
"subscribedOnData": null,
"isBlocked": false
}
}
}messages.deleted
A chat message was deleted.
{
"event": "messages.deleted",
"account_id": "acct_123",
"payload": {
"id": "123"
}
}messages.liked
A chat message was liked. This event is delivered when OnlyFans reports the reaction over the connected account's websocket.
{
"event": "messages.liked",
"account_id": "acct_123",
"payload": {
"id": "1234567890123"
}
}payload.id is the OnlyFans message ID, represented as a string. The reaction event does not include the fan ID, chat ID, message contents, or the time the action happened. Use account_id and a message-to-chat mapping saved from your message history or messages.sent / messages.received events if you need that context.
Normalize message IDs to strings both when storing that mapping and when looking up a reaction, for example with String(payload.id). Sent and received message payloads can contain numeric IDs, while reaction payloads contain strings; normalizing both avoids mismatched typed map keys.
messages.unliked
A like was removed from a chat message. Subscribe to both messages.liked and messages.unliked if you need to track changes to a message's reaction state.
{
"event": "messages.unliked",
"account_id": "acct_123",
"payload": {
"id": "1234567890123"
}
}The payload has the same fields and limitations as messages.liked.
Repeated reactions and retries
A message can be liked, unliked, and liked again. Each change is a separate event, so do not deduplicate using only the message ID or the combination of message ID and event name. Use the X-OFAPI-Idempotency-Key header, when present, to recognise a retry of the same event. Webhook retries can arrive after newer events, so delivery order should not be treated as authoritative message state.
Reaction events do not indicate that a message was read or that a PPV was purchased. Use messages.ppv.unlocked for PPV purchases. Message pin/unpin webhook events are not currently available.
To read or change pins, use List Pinned Chat Messages, Pin Message, and Unpin Message. The existing filter=pinned option on List Chat Messages remains supported.
Chat Queue
chat_queue.updated
A queued message batch has been updated (e.g. progress change).
{
"event": "chat_queue.updated",
"account_id": "acct_123",
"payload": {
"id": 123,
"date": "2025-01-01T00:00:00+00:00",
"canUnsend": true,
"hasError": false,
"isCanceled": false,
"isCouplePeopleMedia": false,
"isDone": false,
"isReady": true,
"pending": 6,
"total": 7,
"unsendSeconds": 1000000
}
}chat_queue.finished
A queued message batch has finished sending.
{
"event": "chat_queue.finished",
"account_id": "acct_123",
"payload": {
"id": 123,
"date": "2025-01-01T00:00:00+00:00",
"canUnsend": false,
"hasError": false,
"isCanceled": true,
"isCouplePeopleMedia": false,
"isDone": true,
"isReady": true,
"pending": 0,
"total": 7,
"unsendSeconds": 0
}
}Tips
tips.received
A new tip has been received from a fan.
{
"event": "tips.received",
"account_id": "acct_123",
"payload": {
"amountGross": 5,
"amountNet": 4,
"fanData": {
"available": true,
"last_updated_at": "2026-01-02 17:12:00",
"spending": {
"total": 6.6,
"messages": 0,
"subscribes": 2.6,
"posts": 0,
"tips": 4,
"streams": 0
}
},
"id": "123123123123",
"type": "tip",
"createdAt": "2026-01-02T17:12:00+00:00",
"text": "paid you a tip of $5.00",
"replacePairs": {
"{SUBSCRIBER_LINK}": "<a href='https:\/\/onlyfans.com\/xxxxxxxx'>John Doe<\/a>",
"{AMOUNT}": "$5.00",
"{POST_BLOCK}": "",
"{POST_DATA}": ""
},
"subType": "new_tips",
"user_id": "9999999999",
"isRead": false,
"canGoToProfile": true,
"user": {
"avatar": "https:\/\/public.onlyfans.com\/files\/u\/uf\/ufs\/ufsXXXXXXXXXXXXX\/111111111\/avatar.jpg",
"avatarThumbs": {
"c50": "https:\/\/thumbs.onlyfans.com\/public\/files\/thumbs\/c50\/u\/uf\/ufs\/ufsXXXXXXXXXXXXX\/111111111\/avatar.jpg",
"c144": "https:\/\/thumbs.onlyfans.com\/public\/files\/thumbs\/c144\/u\/uf\/ufs\/ufsXXXXXXXXXXXXX\/111111111\/avatar.jpg"
},
"header": "https:\/\/public.onlyfans.com\/files\/g\/g2\/g2e\/g2eXXXXXXXXXXXXXXX\/111111111\/header_image.jpg",
"headerSize": {
"width": 3362,
"height": 1780
},
"headerThumbs": {
"w480": "https:\/\/thumbs.onlyfans.com\/public\/files\/thumbs\/w480\/g\/g2\/g2e\/g2eXXXXXXXXXXXXXXX\/111111111\/header_image.jpg",
"w760": "https:\/\/thumbs.onlyfans.com\/public\/files\/thumbs\/w760\/g\/g2\/g2e\/g2eXXXXXXXXXXXXXXX\/111111111\/header_image.jpg"
},
"id": 111111111,
"name": "John Doe",
"username": "johndoe",
"canLookStory": false,
"canCommentStory": false,
"hasNotViewedStory": false,
"isVerified": false,
"canPayInternal": false,
"hasScheduledStream": false,
"hasStream": false,
"hasStories": false,
"tipsEnabled": false,
"tipsTextEnabled": true,
"tipsMin": 5,
"tipsMinInternal": 1,
"tipsMax": 200,
"canEarn": false,
"canAddSubscriber": false,
"subscribePrice": 0,
"subscribedBy": false,
"subscribedOn": false,
"canReceiveChatMessage": false,
"hideChat": false,
"lastSeen": "2026-01-02T17:09:46+00:00",
"isPerformer": false,
"isRealPerformer": false,
"view": "mn",
"canReport": true,
"subscribedByData": null,
"subscribedOnData": null,
"isBlocked": false
}
}
}Subscriptions
subscriptions.new
A new fan has subscribed.
{
"event": "subscriptions.new",
"account_id": "acct_123",
"payload": {
"id": "123",
"type": "subscribed",
"createdAt": "2025-05-05T21:27:00+00:00",
"text": "subscribed to your profile!",
"replacePairs": {
"{SUBSCRIBER_LINK}": "<a href='https://onlyfans.com/username'>Name</a>",
"{PRICE}": "free"
},
"subType": "new_subscriber",
"user_id": "123",
"isRead": false,
"canGoToProfile": true,
"fanData": {
"available": true,
"last_updated_at": "2026-01-02 17:12:00",
"spending": {
"total": 6.6,
"messages": 0,
"subscribes": 2.6,
"posts": 0,
"tips": 4,
"streams": 0
}
},
"user": {
"avatar": "https://example.com",
"avatarThumbs": {
"c50": "https://example.com",
"c144": "https://example.com"
},
"header": "https://example.com",
"headerSize": {
"width": 767,
"height": 1152
},
"headerThumbs": {
"w480": "https://example.com",
"w760": "https://example.com"
},
"id": 123,
"name": "Name",
"username": "username",
"canLookStory": false,
"canCommentStory": false,
"hasNotViewedStory": false,
"isVerified": true,
"canPayInternal": false,
"hasScheduledStream": false,
"hasStream": false,
"hasStories": false,
"tipsEnabled": true,
"tipsTextEnabled": true,
"tipsMin": 5,
"tipsMinInternal": 1,
"tipsMax": 200,
"canEarn": true,
"canAddSubscriber": false,
"subscribePrice": 0,
"subscribedBy": false,
"subscribedOn": false,
"canReceiveChatMessage": false,
"hideChat": false,
"lastSeen": "2025-05-05T21:26:04+00:00",
"isPerformer": true,
"isRealPerformer": true,
"view": "mn",
"canReport": true,
"subscribedByData": null,
"subscribedOnData": null,
"isBlocked": false
}
}
}subscriptions.renewed
A fan has renewed their subscription.
{
"event": "subscriptions.renewed",
"account_id": "acct_123",
"payload": {
"id": "123",
"type": "subscribed",
"createdAt": "2025-05-05T21:27:00+00:00",
"text": "Good news! <a href='https://onlyfans.com/u34547118'>u34547118</a> has Just restarted their monthly subscription to your OnlyFans profile!",
"replacePairs": {
"{SUBSCRIBER_LINK}": "<a href='https://onlyfans.com/u34547118'>u34547118</a>",
"{PRICE}": "$4.00"
},
"subType": "returning_subscriber",
"user_id": "34547118",
"isRead": false,
"canGoToProfile": true,
"fanData": {
"available": true,
"last_updated_at": "2026-01-02 17:12:00",
"spending": {
"total": 6.6,
"messages": 0,
"subscribes": 2.6,
"posts": 0,
"tips": 4,
"streams": 0
}
},
"user": {
"avatar": "https://example.com",
"avatarThumbs": {
"c50": "https://example.com",
"c144": "https://example.com"
},
"header": "https://example.com",
"headerSize": {
"width": 767,
"height": 1152
},
"headerThumbs": {
"w480": "https://example.com",
"w760": "https://example.com"
},
"id": 34547118,
"name": "Name",
"username": "username",
"canLookStory": false,
"canCommentStory": false,
"hasNotViewedStory": false,
"isVerified": true,
"canPayInternal": false,
"hasScheduledStream": false,
"hasStream": false,
"hasStories": false,
"tipsEnabled": true,
"tipsTextEnabled": true,
"tipsMin": 5,
"tipsMinInternal": 1,
"tipsMax": 200,
"canEarn": true,
"canAddSubscriber": false,
"subscribePrice": 0,
"subscribedBy": false,
"subscribedOn": false,
"canReceiveChatMessage": false,
"hideChat": false,
"lastSeen": "2025-05-05T21:26:04+00:00",
"isPerformer": true,
"isRealPerformer": true,
"view": "mn",
"canReport": true,
"subscribedByData": null,
"subscribedOnData": null,
"isBlocked": false
}
}
}subscriptions.expired
A fan's subscription has expired.
Unlike subscriptions.new and subscriptions.renewed, this event is derived. OnlyFans
emits no expiry signal, so we track the subscription end date it reports for each of your
subscribers, notice when one passes, and confirm the lapse against OnlyFans before delivering
it. Expect delivery within about 15 minutes of the subscription lapsing.
Four things worth planning for:
- Subscriptions that had already lapsed before you enabled this event are not backfilled — you only receive lapses that happen from the moment you subscribe onwards. If another webhook on your team already watched that creator, you may receive lapses from shortly before you subscribed. The same bound applies after any interruption on our side: we resume from the last 48 hours rather than replaying further back.
- Use
expiredAtrather than the delivery time to decide when a subscription actually ended. It is the value OnlyFans reports, and it is fixed at the moment we detect the lapse, so it stays correct even if the fan resubscribes before the webhook reaches you. - A fan who renews instead of lapsing produces no event. We check with OnlyFans immediately before sending, so a renewal that lands first is never reported as a lapse. If they later lapse for real, the event fires then.
- Coverage follows the subscribers we hold a current end date for. Every subscription that can later lapse was first created or renewed, and both carry that date, so in practice this is your whole active base. A subscription we have never observed — one that started before the account was connected and has not renewed since — can be missed.
{
"event": "subscriptions.expired",
"account_id": "acct_123",
"payload": {
"id": "34547118:2025-05-05T21:27:00+00:00",
"type": "subscription_expired",
"subType": "expired_subscriber",
"createdAt": "2025-05-05T21:34:12+00:00",
"expiredAt": "2025-05-05T21:27:00+00:00",
"user_id": "34547118",
"user": {
"avatar": "https://example.com",
"avatarThumbs": {
"c50": "https://example.com",
"c144": "https://example.com"
},
"header": "https://example.com",
"headerSize": {
"width": 767,
"height": 1152
},
"headerThumbs": {
"w480": "https://example.com",
"w760": "https://example.com"
},
"id": 34547118,
"name": "Name",
"username": "username",
"canLookStory": false,
"canCommentStory": false,
"hasNotViewedStory": false,
"isVerified": true,
"canPayInternal": false,
"hasScheduledStream": false,
"hasStream": false,
"hasStories": false,
"tipsEnabled": true,
"tipsTextEnabled": true,
"tipsMin": 5,
"tipsMinInternal": 1,
"tipsMax": 200,
"canEarn": true,
"canAddSubscriber": false,
"subscribePrice": 12.99,
"subscribedBy": false,
"subscribedOn": false,
"canReceiveChatMessage": false,
"hideChat": false,
"lastSeen": "2025-05-04T09:12:31+00:00",
"isPerformer": true,
"isRealPerformer": true,
"view": "f",
"canReport": true,
"subscribedByData": null,
"subscribedOnData": {
"price": 12.99,
"newPrice": 12.99,
"regularPrice": 12.99,
"subscribePrice": 12.99,
"discountPercent": 0,
"discountPeriod": 1,
"subscribeAt": "2025-04-05T21:27:00+00:00",
"expiredAt": "2025-05-05T21:27:00+00:00",
"renewedAt": null,
"discountFinishedAt": null,
"discountStartedAt": null,
"status": null,
"isMuted": false,
"unsubscribeReason": "Redacted",
"duration": "1 month",
"tipsSumm": 15,
"subscribesSumm": 12.99,
"messagesSumm": 40.5,
"postsSumm": 0,
"streamsSumm": 0,
"totalSumm": 68.49,
"subscribes": [],
"hasActivePaidSubscriptions": false
},
"isBlocked": false
},
"fanData": {
"available": true,
"last_updated_at": "2025-05-05T21:34:11.000000Z",
"spending": {
"total": 68.49,
"messages": 40.5,
"subscribes": 12.99,
"posts": 0,
"tips": 15,
"streams": 0
},
"previous_reply_at": "2025-04-29T14:02:55.000000Z"
}
}
}Posts
posts.liked
A fan has liked one of your posts.
{
"event": "posts.liked",
"account_id": "acct_123",
"payload": {
"id": "123",
"type": "favorited",
"createdAt": "2025-05-05T22:22:00+00:00",
"text": "liked your <a href='https://onlyfans.com/123/username'>post</a>",
"replacePairs": {
"{POST_LINK}": "<a href='https://onlyfans.com/123/username'>post</a>"
},
"subType": "new_favorite",
"user_id": "123",
"isRead": false,
"canGoToProfile": true,
"user": {
"avatar": null,
"avatarThumbs": null,
"header": null,
"headerSize": null,
"headerThumbs": null,
"id": 123,
"name": "Name",
"username": "username",
"canLookStory": false,
"canCommentStory": false,
"hasNotViewedStory": false,
"isVerified": false,
"canPayInternal": false,
"hasScheduledStream": false,
"hasStream": false,
"hasStories": false,
"tipsEnabled": false,
"tipsTextEnabled": true,
"tipsMin": 5,
"tipsMinInternal": 1,
"tipsMax": 200,
"canEarn": false,
"canAddSubscriber": false,
"subscribePrice": 0,
"subscribedBy": false,
"subscribedOn": false,
"canReceiveChatMessage": false,
"hideChat": false,
"lastSeen": null,
"isPerformer": false,
"isRealPerformer": false,
"view": "mn",
"canReport": true,
"subscribedByData": null,
"subscribedOnData": null,
"isBlocked": false
}
}
}Users
users.typing
A fan is typing a message.
{
"event": "users.typing",
"account_id": "acct_123",
"payload": {
"id": 1910197
}
}users.online
A fan was detected as online.
Recent messages.liked and messages.unliked events can also confirm that a fan is online when OFAPI can match the reaction to one stored, direct message sent by the creator. Reactions to queued messages, missing or ambiguous message records, and stale activity are ignored for presence; the reaction webhook is still delivered.
Subscribe to users.online separately to receive these presence transitions. Further reactions refresh activity but do not emit another users.online event while the fan is already online.
{
"event": "users.online",
"account_id": "acct_123",
"payload": {
"fan": {
"id": "123",
"username": "johndoe",
"name": "John Doe",
"display_name": "John Doe",
"spending": {
"messages": 10,
"subscribes": 2.6,
"posts": 0,
"tips": 4,
"streams": 0,
"total": 16.6
}
},
"observed_at": "2026-01-02T17:12:00.000000Z",
"status_changed_at": "2026-01-02T17:12:00.000000Z",
"last_seen_online_at": "2026-01-02T17:12:00.000000Z"
}
}users.offline
A fan that was previously detected as online was detected as offline.
{
"event": "users.offline",
"account_id": "acct_123",
"payload": {
"fan": {
"id": "123",
"username": "johndoe",
"name": "John Doe",
"display_name": "John Doe",
"spending": {
"messages": 10,
"subscribes": 2.6,
"posts": 0,
"tips": 4,
"streams": 0,
"total": 16.6
}
},
"observed_at": "2026-01-02T17:42:00.000000Z",
"status_changed_at": "2026-01-02T17:42:00.000000Z",
"last_seen_online_at": "2026-01-02T17:12:00.000000Z"
}
}These events are detected by periodic online-status polling, so delivery is not instant. users.online is emitted when a tracked fan transitions from unknown/offline to online. users.offline is emitted when a fan that was previously detected online is no longer present in a successful online-status check.
Data Exports
Data export webhook events are team-level events and do not include an account_id at the top level. Instead, the account_ids array is included in the payload.
data_exports.calculating_credits
Credit calculation has started for a data export.
{
"event": "data_exports.calculating_credits",
"payload": {
"id": "data_export_abc123",
"type": "transactions",
"status": "calculating_credits",
"file_type": "csv",
"credit_cost": null,
"start_date": "2025-01-01T00:00:00+00:00",
"end_date": "2025-01-31T23:59:59+00:00",
"account_ids": ["acct_123", "acct_456"],
"created_at": "2025-05-13T20:26:56+00:00"
}
}data_exports.calculating_credits_completed
Credit calculation has completed successfully for a data export.
{
"event": "data_exports.calculating_credits_completed",
"payload": {
"id": "data_export_abc123",
"type": "transactions",
"status": "calculating_credits_completed",
"file_type": "csv",
"credit_cost": 150,
"start_date": "2025-01-01T00:00:00+00:00",
"end_date": "2025-01-31T23:59:59+00:00",
"account_ids": ["acct_123", "acct_456"],
"created_at": "2025-05-13T20:26:56+00:00"
}
}data_exports.calculating_credits_failed
Credit calculation has failed for a data export.
{
"event": "data_exports.calculating_credits_failed",
"payload": {
"id": "data_export_abc123",
"type": "transactions",
"status": "calculating_credits_failed",
"file_type": "csv",
"credit_cost": null,
"start_date": "2025-01-01T00:00:00+00:00",
"end_date": "2025-01-31T23:59:59+00:00",
"account_ids": ["acct_123", "acct_456"],
"created_at": "2025-05-13T20:26:56+00:00"
}
}data_exports.in_progress
A data export is now in progress.
{
"event": "data_exports.in_progress",
"payload": {
"id": "data_export_abc123",
"type": "transactions",
"status": "in_progress",
"file_type": "csv",
"credit_cost": 150,
"start_date": "2025-01-01T00:00:00+00:00",
"end_date": "2025-01-31T23:59:59+00:00",
"account_ids": ["acct_123", "acct_456"],
"created_at": "2025-05-13T20:26:56+00:00"
}
}data_exports.completed
A data export has completed successfully. The payload includes a temporary download URL that expires after 1 hour.
{
"event": "data_exports.completed",
"payload": {
"id": "data_export_abc123",
"type": "transactions",
"status": "completed",
"file_type": "csv",
"credit_cost": 150,
"start_date": "2025-01-01T00:00:00+00:00",
"end_date": "2025-01-31T23:59:59+00:00",
"account_ids": ["acct_123", "acct_456"],
"created_at": "2025-05-13T20:26:56+00:00",
"download_url": "https://example-bucket.s3.amazonaws.com/data-exports/team-slug/data_export_abc123xyz789.csv?X-Amz-Signature=...",
"download_url_expires_at": "2025-05-13T21:26:56+00:00"
}
}data_exports.failed
A data export has failed. The failed_reason field is only included when the failure was due to insufficient credits.
{
"event": "data_exports.failed",
"payload": {
"id": "data_export_abc123",
"type": "transactions",
"status": "failed",
"file_type": "csv",
"credit_cost": 150,
"start_date": "2025-01-01T00:00:00+00:00",
"end_date": "2025-01-31T23:59:59+00:00",
"account_ids": ["acct_123", "acct_456"],
"created_at": "2025-05-13T20:26:56+00:00",
"failed_reason": "Insufficient credits: 150 required"
}
}data_exports.cancelled
A data export was cancelled.
{
"event": "data_exports.cancelled",
"payload": {
"id": "data_export_abc123",
"type": "transactions",
"status": "cancelled",
"file_type": "csv",
"credit_cost": 150,
"start_date": "2025-01-01T00:00:00+00:00",
"end_date": "2025-01-31T23:59:59+00:00",
"account_ids": ["acct_123", "acct_456"],
"created_at": "2025-05-13T20:26:56+00:00"
}
}Fan Summary
fan_summary.completed
An AI Fan Profile Summary has finished generating. This event fires for both new summaries and regenerations, and is dispatched after the summary completes successfully.
{
"event": "fan_summary.completed",
"data": {
"fan": {
"onlyfans_id": 123456789,
"username": "example_fan"
},
"summary": {
"status": "completed",
"summary_data": {
"name": "Jane Doe",
"preferred_name": "Jane",
"family_pets": "Has a golden retriever named Max",
"travel_plans": "Planning a trip to Bali",
"kinks": "",
"hobbies": "Yoga, photography",
"content_preferences": "Behind the scenes content",
"themes": "",
"requests": "",
"interests": "Fitness, travel",
"other_notes": ""
},
"analyzed_message_count": 504,
"last_analyzed_at": "2026-03-19T12:00:00+00:00"
}
}
}Media Uploads
These events only fire for asynchronous uploads (async=true). A synchronous upload returns its result in the response body, so there is nothing left to notify you about.
media_uploads.completed
An async media upload finished successfully. Fires for both Upload Media to OnlyFans CDN and Upload Media to Vault.
The payload carries the same fields as Get Upload Status, so you can subscribe to this event instead of polling the polling_url you got back from the 202 response.
{
"event": "media_uploads.completed",
"account_id": "acct_123",
"payload": {
"id": "ofapi_media_abc123def456abc123def456abc123de",
"prefixed_id": "ofapi_media_abc123def456abc123def456abc123de",
"account_id": "acct_123",
"status": "completed",
"file_name": "photo.jpg",
"created_at": "2026-04-03T16:17:58+00:00",
"media_id": 1234567890,
"media": {
"id": 1234567890,
"type": "photo",
"convertedToVideo": false,
"canView": true,
"hasError": false,
"createdAt": "2026-04-03T16:18:05+00:00",
"isReady": false,
"files": {
"full": { "url": null, "width": 0, "height": 0, "size": 0, "sources": [] },
"thumb": null,
"preview": null,
"squarePreview": null
},
"duration": 0,
"releaseForms": [],
"hasCustomPreview": false
},
"credits_used": 3,
"completed_at": "2026-04-03T16:18:05+00:00"
}
}Vault uploads carry the OnlyFans media ID in media_id. CDN uploads do not have one yet — they are referenced by prefixed_id (or media.processId) until you attach them to a post or message — so media_id is null there:
{
"event": "media_uploads.completed",
"account_id": "acct_123",
"payload": {
"id": "ofapi_media_abc123def456abc123def456abc123de",
"prefixed_id": "ofapi_media_abc123def456abc123def456abc123de",
"account_id": "acct_123",
"status": "completed",
"file_name": "photo.jpg",
"created_at": "2026-04-03T16:17:58+00:00",
"media_id": null,
"media": {
"prefixed_id": "ofapi_media_abc123def456abc123def456abc123de",
"file_name": "photo.jpg",
"processId": "abc123def456abc123de",
"host": "convert3.onlyfans.com",
"sourceUrl": "https://of2transcoder.s3.amazonaws.com/upload/00000000-0000-0000-0000-000000000000/000000000000/photo.jpg",
"extra": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx...",
"additional": { "user": "123456789" },
"thumbs": [
{
"id": 1,
"url": "https://cdn2.onlyfans.com/files/0/00/00000000000000000000000000000000/300x544_00000000000000000000000000000000.jpg"
}
]
},
"credits_used": 3,
"completed_at": "2026-04-03T16:18:05+00:00"
}
}The prefixed_id can only be used once. After you include it in a post or message it is no longer valid, and you must upload the file again to get a new one.
media_uploads.failed
An async media upload failed. The error field carries the reason, and no credits are charged for the upload itself.
{
"event": "media_uploads.failed",
"account_id": "acct_123",
"payload": {
"id": "ofapi_media_abc123def456abc123def456abc123de",
"prefixed_id": "ofapi_media_abc123def456abc123def456abc123de",
"account_id": "acct_123",
"status": "failed",
"file_name": "photo.jpg",
"created_at": "2026-04-03T16:17:58+00:00",
"error": "Failed to download file from the provided URL.",
"failed_at": "2026-04-03T16:18:05+00:00"
}
}Common error values include a failed download from your file_url, a file larger than your subscription's upload limit, not enough credits to cover the upload, and Async upload staging expired before processing completed. Please retry. for an upload that sat unprocessed for more than 24 hours.
Protecting your webhooks
It is recommended to validate incoming webhook requests to ensure that they originate from OnlyFans API, and not from a malicious actor. You can do this by verifying the `Signature` header in the request.
Fansly events
A list of all available Fansly webhook events that you can subscribe to.