For the complete documentation index, see llms.txt. This page is also available as Markdown.

Webhook Integration

Webhooks allow you to receive real-time HTTP notifications when events occur in your OnlyMonster account. Instead of polling for changes, your server receives a POST request with event data as soon as something happens.

Note: Webhook event types are currently in active development. This page will be updated as new events become available.

Setup

  1. Log in to your OnlyMonster dashboard

  2. Navigate to API → Webhooks

  3. Enter your HTTPS endpoint URL and click Save

  4. Copy and securely store the webhook secret

Important: The webhook secret is displayed only once. Save it in a secure location immediately. If you lose it, you will need to create a new webhook.

You can view or delete your active webhook from the same API → Webhooks tab.

Note: It can take up to 1 minute after you save a new webhook before events start being delivered.

Receiving Webhooks

When an event occurs, OnlyMonster sends an HTTP POST request to your configured URL with the following headers:

Header
Description

content-type

Always application/json

x-om-webhook-signature

HMAC-SHA256 hex-encoded signature

x-om-webhook-timestamp

ISO 8601 timestamp of when the event was sent

x-om-webhook-id

UUID unique to this delivery attempt — use it for deduplication if you process retries

Payload Format

The request body is a JSON object with the following structure:

  • type — a string identifying the event (e.g. chat.message, chat.message_sent)

  • payload — an object containing event-specific data

Verifying Signatures

Every webhook request is signed using your secret so you can verify it was sent by OnlyMonster and hasn't been tampered with.

To verify a webhook signature:

  1. Get the x-om-webhook-timestamp and x-om-webhook-signature headers from the request

  2. Construct the signed content by concatenating the timestamp, a dot (.), and the raw request body:

  3. Compute an HMAC-SHA256 hash of the signed content using your webhook secret

  4. Compare the hex-encoded result with x-om-webhook-signature

Node.js / TypeScript

Go

Event Types

All events follow a consistent structure:

The Source column in the field tables indicates whether a value originates from OnlyFans (so it follows OnlyFans formats and lifecycle — IDs match what you'd see in the OF API) or from OnlyMonster (identifiers and metadata generated on our side).

Shared payload.account object

Account-scoped events (e.g. chat.message, vault.media_upload.created, vault.media_upload.updated, firewall.message_guard.violation.user, firewall.message_guard.violation.om_api, fans.ppv.purchased, fans.subscription.new_subscriber, fans.tip.received) include an account object on payload that identifies which account the event belongs to:

Field
Type
Source
Description

account_id

string

OnlyMonster

OnlyMonster account ID — stable across platform reconnects.

platform_account_id

string

OnlyFans

OnlyFans user ID of the connected account (your account, not the fan's).

chat.message

Fires when a chat message is observed on one of your connected OnlyFans accounts. This includes both messages a fan sends to you and messages sent from your account to a fan.

Example:

Fields (payload):

Field
Type
Source
Description

account

object

mixed

Identifies the account this event belongs to. See the shared payload.account table above.

message

object

OnlyFans

The chat message. See payload.message fields below.

Fields (payload.message):

Field
Type
Source
Description

message_id

string

OnlyFans

OnlyFans message ID.

fan_id

string

OnlyFans

OnlyFans user ID of the fan in the conversation (always the other party — never your own account).

from_id

string

OnlyFans

OnlyFans user ID of the sender.

created_at

string (ISO 8601)

OnlyFans

When the message was created on OnlyFans.

text

string | null

OnlyFans

Message text. null for media-only messages.

medias

Record<string, { type: string }>

OnlyFans

Map of attached media keyed by OnlyFans media ID. Each value contains the media type (e.g. "photo", "video", "gif", "audio").

Determining message direction:

The payload does not include an explicit direction field. Compare from_id and fan_id to tell incoming from outgoing:

  • from_id === fan_idincoming — the fan sent the message to you.

  • from_id !== fan_idoutgoing — you sent the message to the fan.

Restored messages:

If our connection to OnlyFans is briefly lost, missed activity is reconciled when the connection is restored. For each chat that had new activity during the outage, we deliver only the most recent message in that chat — intermediate messages sent during the outage are not backfilled. These deliveries include an additional metadata object on the payload:

Field
Type
Source
Description

is_restored

boolean (optional)

OnlyMonster

true when the message was backfilled after a brief disconnect rather than received in real time.

connection_lost_at

string (ISO 8601, optional)

OnlyMonster

When the disconnect started — bounds the period these restored messages cover.

For restored messages, medias is delivered as an empty object ({}) — media metadata is not available from the backfill source.

chat.message_sent

Fires when a message you submitted via the OnlyMonster API is successfully delivered to OnlyFans. This event is only sent for messages you submit through the public API — messages sent by other OnlyMonster features do not trigger this webhook.

Example:

Fields (payload):

Field
Type
Source
Description

send_id

string

OnlyMonster

Identifier returned to you when you submitted the send request — use it to correlate.

account_id

string

OnlyMonster

OnlyMonster account ID the message was sent from.

fan_id

string

OnlyFans

OnlyFans user ID of the recipient.

platform_message_id

string

OnlyFans

OnlyFans message ID assigned to the delivered message. The corresponding chat.message event for this delivery will carry the same value in payload.message.message_id.

chat.message_error

Fires when a message you submitted via the OnlyMonster API fails to be delivered to OnlyFans. As with chat.message_sent, this event is only sent for messages submitted through the public API.

Example:

Fields (payload):

Field
Type
Source
Description

send_id

string

OnlyMonster

Identifier returned to you when you submitted the send request — use it to correlate.

account_id

string

OnlyMonster

OnlyMonster account ID the message was attempted from.

fan_id

string

OnlyFans

OnlyFans user ID of the intended recipient.

status

"restricted" | "failed"

OnlyMonster

restricted — the recipient has blocked or restricted messages. failed — any other delivery failure.

vault.media_upload.created

Fires when a new media upload is created in your OnlyMonster vault. The upload starts in the uploaded state and transitions through processing/exporting states; subsequent transitions are delivered as vault.media_upload.updated events.

Example:

Fields (payload):

Field
Type
Source
Description

media_upload_id

string

OnlyMonster

Stable OnlyMonster identifier for this upload — use it to correlate created and subsequent updated events.

media_id

string | null

OnlyFans

OnlyFans media ID assigned once the upload has been exported to the platform. null until export completes.

status

string (enum)

OnlyMonster

Current upload state. One of uploaded, processing, processed, exporting, exported, failed, unprocessable.

account

object

mixed

Identifies the account this upload belongs to. See the shared payload.account table above.

created_at

string (ISO 8601)

OnlyMonster

When the upload row was created.

updated_at

string (ISO 8601)

OnlyMonster

When the upload row was last persisted. Equal to created_at for a freshly created upload.

Note: Ordering and dedup guidance is shared with vault.media_upload.updated below — read it before storing state.

vault.media_upload.updated

Fires on any persisted change to an existing vault media upload — most commonly a status transition (e.g. processing → processed) or an assignment of media_id once the upload is processed.

Important:

  • Consumers should not assume a specific delta from a single event — read status (and media_id) on every event and reconcile against media_upload_id.

  • Events for the same media_upload_id may arrive out of order. Order them by updated_at before applying — do not flip a stored status backward just because a newer event was received first.

  • Vault events may be delivered more than once (e.g. when a previous attempt's response was lost in flight) — treat (media_upload_id, updated_at) as the dedup key. x-om-webhook-id changes on every retry attempt and is not suitable for cross-retry deduplication.

Example:

Fields (payload): Same as vault.media_upload.created above. updated_at advances on each transition.

firewall.message_guard.violation.user / firewall.message_guard.violation.om_api

Fires when Message Guard — OnlyMonster's content moderation feature — blocks an outgoing message because it contains restricted content. The payload identifies the account, the chat where the violation occurred, the message text, and the specific words and topics that were detected.

The event is split by who triggered the blocked message, so you can subscribe to one or both:

  • firewall.message_guard.violation.user — a human operator sent the message.

  • firewall.message_guard.violation.om_api — the message was sent via the OnlyMonster API.

Both event types carry the same payload shape; only the type and which principal-id field is populated differ (see user_id / om_api_token_id below).

Example:

Fields (payload):

Field
Type
Source
Description

violation_id

string

OnlyMonster

Stable identifier for this violation — use it as a deduplication key.

account

object

mixed

Identifies the account this violation belongs to. See the shared payload.account table above.

user_id

string | null

OnlyMonster

OnlyMonster-internal id of the operator who sent the message. Populated for ...violation.user; null for ...violation.om_api.

om_api_token_id

string | null

OnlyMonster

Id of the OnlyMonster API token that sent the message. Populated for ...violation.om_api; null for ...violation.user.

context

string (enum)

OnlyMonster

What kind of content triggered the check. One of message, mass_message, post, comment, auto_message.

chat_link

string | null

OnlyFans

URL to the OnlyFans chat where the violation occurred. null for violations outside a chat context.

message

string

OnlyMonster

The full text of the operator's message that triggered the violation.

violation

object

OnlyMonster

Details of what was detected. See payload.violation fields below.

created_at

string (ISO 8601)

OnlyMonster

When the violation was recorded.

Fields (payload.violation):

Field
Type
Source
Description

restricted_words

string[]

OnlyMonster

Specific words or phrases from your organisation's restricted-words list that matched the message text.

topics

string[]

OnlyMonster

Higher-level topical categories the message was classified into (e.g. finance, double_meaning). Includes both word-list topic tags and AI-inferred categories.

Deduplication: violation_id is stable across retries. Use it as your dedup key — x-om-webhook-id changes on every retry attempt and is not suitable for cross-retry deduplication.

fans.subscription.new_subscriber

Fires the first time a fan subscribes to one of your connected OnlyFans accounts.

Only a fan's first-ever subscription to a given account fires this event. The following do not fire it:

  • Renewals — an active subscription continuing into the next billing period.

  • Re-subscriptions — a fan who subscribed to this account before, let it lapse, and subscribed again.

You therefore receive it at most once per (account_id, fan_id) pair — see Deduplication below for the at-least-once delivery caveat.

Example:

Fields (payload):

Field
Type
Source
Description

fan_id

string

OnlyFans

OnlyFans user ID of the fan who started the subscription.

account

object

mixed

Identifies the account this subscription belongs to. See the shared payload.account table above.

Deduplication: the event targets each (account_id, fan_id) pair once, but — like every webhook — it is delivered at least once, so a retry or a rare producer-side overlap can re-deliver the same new subscriber. Use the (account_id, fan_id) pair as your dedup key — x-om-webhook-id changes on every retry attempt and is not suitable for cross-retry deduplication.

fans.ppv.purchased

Fires when a fan purchases pay-per-view content on one of your connected OnlyFans accounts — either a paid direct message (PPV message) or a locked post. The content_type field discriminates the two; content_id is the OnlyFans message id or post id respectively.

Example:

Fields (payload):

Field
Type
Source
Description

content_type

string (enum)

OnlyFans

What was purchased. One of message (paid DM) or post (locked post).

content_id

string

OnlyFans

OnlyFans id of the purchased content — a message id when content_type is message, a post id when it is post.

fan_id

string

OnlyFans

OnlyFans id of the fan who made the purchase.

price_gross

number

OnlyFans

Gross amount the fan paid.

account

object

mixed

Identifies the account this purchase belongs to. See the shared payload.account table above.

purchased_at

string (ISO 8601)

OnlyFans

When the purchase occurred. Sourced from the real-time notification and has minute precision (seconds are always :00); do not rely on sub-minute ordering.

Source & timing: This event is detected in near-real time from the OnlyFans notification stream as soon as the purchase notification arrives — typically within seconds of the purchase. It is suitable for low-latency reactions.

Deduplication: Use (account_id, content_type, content_id, fan_id) as your dedup key. x-om-webhook-id changes on every retry attempt and is not suitable for cross-retry deduplication. Note purchased_at has only minute precision and must not be relied on for uniqueness.

fans.tip.received

Fires when a fan sends a tip on one of your connected OnlyFans accounts.

Example:

Fields (payload):

Field
Type
Source
Description

tip_id

string

OnlyFans

Stable identifier for this tip (the OnlyFans notification id). Use this as your deduplication key — it is stable across delivery retries.

fan_id

string

OnlyFans

OnlyFans id of the fan who sent the tip.

amount_gross

number

OnlyFans

Gross amount the fan tipped.

account

object

mixed

Identifies the account this tip belongs to. See the shared payload.account table above.

tipped_at

string (ISO 8601)

OnlyFans

When the tip was sent. Sourced from the real-time notification and has minute precision (seconds are always :00); do not rely on sub-minute ordering.

Source & timing: Detected in near-real time from the OnlyFans notification stream as soon as the tip notification arrives — typically within seconds of the tip. Suitable for low-latency reactions.

Deduplication: Use tip_id as your dedup key — it is the OnlyFans notification id for the tip and is stable across delivery retries, so it correctly collapses retried deliveries of the same tip. Do not use x-om-webhook-id — it changes on every retry attempt.

Retry & Delivery Behavior

Behavior
Details

Timeout

15 seconds per request

Retries

Up to 3 attempts with 15-second delay

Retry condition

5xx server errors and network failures

No retry

2xx (success), 3xx, 4xx responses

Redirects

Not followed

Your endpoint must respond with an HTTP 2xx status code to acknowledge receipt. Any 5xx response or network failure will trigger a retry.

Backpressure during sustained timeouts

To protect both your endpoint and our delivery pipeline, we apply per-webhook backpressure when an endpoint stops responding entirely. If a webhook's endpoint times out (no response within the 15-second window) on several consecutive deliveries, we temporarily pause delivery to that webhook for ~1 minute. Events that fire during the pause window are dropped — not retried. After the pause, we send a single probe; if it succeeds, normal delivery resumes immediately, otherwise the pause renews. Backpressure is isolated per webhook target, so a slow endpoint on one webhook does not pause delivery to your other webhooks.

Backpressure is triggered only by request timeouts. 5xx responses, network errors (e.g. connection refused, DNS), and SSRF/HTTP-blocked targets do not trigger it — they follow the standard retry table above.

If you need a complete history of events, treat webhooks as a real-time signal and reconcile against the corresponding polling endpoints periodically. Backpressure pauses are observable on your side as a gap with no delivery attempts.

Best Practices

  • Verify signatures — Always verify the x-om-webhook-signature before processing any event to ensure authenticity.

  • Respond quickly — Return a 200 response immediately, then process the event asynchronously. Long-running processing may cause timeouts.

  • Use HTTPS — Webhook URLs must use HTTPS. HTTP endpoints are rejected.

  • Secure your secret — The webhook secret is shown only once during setup. Store it in a secrets manager or environment variable.

Last updated

Was this helpful?