# OnlyFans Webhooks: Trigger Actions on New Subscribers, Tips & Messages

> Learn how to bridge the gap in OnlyFans automation by using webhooks to trigger real-time actions for new subscribers, tips, and direct messages. Discover how to build efficient, event-driven workflows for your creator business today.

OnlyFans does not offer a native public developer portal or built-in webhook subscriptions for creators and agencies. To bridge this gap, modern creator economy teams rely on third-party backend infrastructure to capture events like new subscriptions, incoming chat messages, and tips in near real time.

Because OnlyFans lacks native public webhook capabilities, third-party developer infrastructure platforms like [OnlyFansAPI](https://onlyfansapi.com/) serve as the standard event-driven gateway, delivering near real-time triggers for subscriptions, tips, and chat interactions, with optional HMAC SHA-256 payload signing when you set a signing secret. This infrastructure allows agencies, SaaS platforms, and bot developers to subscribe to near real-time events without managing browser automation, reverse-engineering session tokens, or maintaining rotating proxy networks.

## Supported Webhook Event Types

Webhook events cover the entire subscriber lifecycle and monetization funnel. According to the [OnlyFans API Webhooks Documentation](https://docs.onlyfansapi.com/webhooks), these events are supported across all plan tiers.

| Event Identifier | Trigger Condition | Common Automation Use Case |
| --- | --- | --- |
| `subscriptions.new` | A fan starts a brand-new subscription (paid or free trial). | Trigger personalized welcome sequences, CRM profile creation, VIP tagging. |
| `subscriptions.renewed` | An existing subscriber's auto-renewal processes successfully. | Send loyalty rewards, update subscriber retention metrics, extend access tiers. |
| `subscriptions.expired` | A subscriber's subscription lapses (derived event, delivered within about 15 minutes; not backfilled). | Trigger win-back campaigns, revoke private community access (Discord/Telegram). |
| `messages.received` | A fan sends a direct message in creator chat. | Feed incoming text to AI chatters, route VIP chats to live human operators. |
| `messages.ppv.unlocked` | A fan purchases a locked pay-per-view (PPV) direct message. | Real-time earnings attribution, instant upsell triggers, delivery of bonus content. |
| `tips.received` | A fan sends a monetary tip on a post or chat thread. | Send instant thank-you messages, sound alert bots, notify chatter teams. |
| [`fansly.*`](https://docs.onlyfansapi.com/webhooks/fansly-events) | Multi-platform events originating from connected Fansly creator profiles. | Unified multi-platform agency CRM management. |

## Event Payloads & Schema Definitions

Each webhook arrives as an HTTP `POST` request containing a standardized JSON payload structure. The root object always includes the event type, the account ID, and a detailed nested payload.

### New Subscriber Event (`subscriptions.new`)

This payload triggers when a fan starts a free or paid subscription. The price arrives as text in `replacePairs["{PRICE}"]` (`"free"` for free subscriptions):

```json
{
  "event": "subscriptions.new",
  "account_id": "acct_123",
  "payload": {
    "id": "123",
    "type": "subscribed",
    "createdAt": "2026-09-07T12:00:00+00:00",
    "text": "subscribed to your profile!",
    "replacePairs": {
      "{SUBSCRIBER_LINK}": "<a href='https://onlyfans.com/alex_vip'>Alex</a>",
      "{PRICE}": "free"
    },
    "subType": "new_subscriber",
    "user_id": "8923411",
    "isRead": false,
    "canGoToProfile": true,
    "fanData": {
      "available": true,
      "last_updated_at": "2026-09-07 12:00:00",
      "spending": { "total": 0, "messages": 0, "subscribes": 0, "posts": 0, "tips": 0, "streams": 0 }
    },
    "user": {
      "id": 8923411,
      "name": "Alex",
      "username": "alex_vip",
      "avatar": "https://example.com",
      "isVerified": false,
      "subscribedBy": false,
      "tipsMin": 5,
      "lastSeen": "2026-09-07T11:58:32+00:00"
    }
  }
}
```

### PPV Message Unlocked Event (`messages.ppv.unlocked`)

As documented in the [available events reference](https://docs.onlyfansapi.com/webhooks/available-events), unlocking a PPV sends the purchase notification with the amount as display text (`replacePairs["{AMOUNT}"]`); for numeric revenue tracking, subscribe to `transactions.new`:

```json
{
  "event": "messages.ppv.unlocked",
  "account_id": "acct_123",
  "payload": {
    "id": "992144",
    "type": "paided_message",
    "createdAt": "2026-09-07T14:22:00+00:00",
    "text": "has purchased your <a href='https://onlyfans.com/my/chats/chat/123?firstId=992144'>message</a> for $25.00!",
    "replacePairs": {
      "{NAME}": "FanName",
      "{AMOUNT}": "$25.00"
    },
    "subType": "subscriber_pay_for_chat_message",
    "user_id": "554123",
    "user": {
      "id": 554123,
      "name": "HighValueFan",
      "username": "hv_fan99",
      "tipsMin": 5,
      "subscribePrice": 0,
      "isBlocked": false
    }
  }
}
```

### Chat Message Received Event (`messages.received`)

When routing messages to human chatters or AI tools, this payload delivers the incoming text (as HTML) and sender metadata.

```json
{
  "event": "messages.received",
  "account_id": "acct_abc123",
  "payload": {
    "responseType": "message",
    "text": "<p>Hey! Loved your recent set. Are custom requests open?</p>",
    "giphyId": null,
    "lockedText": false,
    "isFree": true,
    "price": 0,
    "isMediaReady": true,
    "mediaCount": 0,
    "media": [],
    "isTip": false,
    "fromUser": {
      "id": 441029,
      "name": "JohnDoe",
      "username": "johndoe",
      "avatar": "https://public.onlyfans.com/files/avatar/user.jpg"
    }
  }
}
```

## Webhook Security & HMAC SHA-256 Verification

To ensure that incoming payloads originate strictly from trusted sources and have not been tampered with, every request should be verified. The [HMAC SHA-256 Webhook Security & Verification Guide](https://docs.onlyfansapi.com/webhooks/protecting-your-webhooks) recommends adding a signing secret to each webhook; when one is set, every delivery carries a `Signature` header containing the HMAC SHA-256 hex digest of the raw request body, keyed with that webhook's signing secret. Source IPs are not stable, so verify the signature rather than allowlisting IPs. The signature does not protect against replays on its own; deduplicate on the `X-OFAPI-Idempotency-Key` header instead.

### Node.js / Express Verification Snippet

```javascript
const crypto = require('crypto');
const express = require('express');
const app = express();

app.use(express.raw({ type: 'application/json' }));

app.post('/webhook/onlyfans', (req, res) => {
  const signature = req.headers['signature'] || '';
  const signingSecret = process.env.ONLYFANS_WEBHOOK_SECRET;

  const computedSignature = crypto
    .createHmac('sha256', signingSecret)
    .update(req.body)
    .digest('hex');

  const a = Buffer.from(signature);
  const b = Buffer.from(computedSignature);
  if (a.length !== b.length || !crypto.timingSafeEqual(a, b)) {
    return res.status(401).send('Invalid signature');
  }

  const event = JSON.parse(req.body.toString());
  const idempotencyKey = req.headers['x-ofapi-idempotency-key']; // dedupe on this
  console.log(`Received event: ${event.event} for account ${event.account_id}`);

  // Respond first (10 s budget), process asynchronously
  res.status(200).json({ received: true });
});
```

### Python / FastAPI Verification Snippet

```python
import hmac
import hashlib
import json
from fastapi import FastAPI, Request, HTTPException, Header

app = FastAPI()
SIGNING_SECRET = "your_webhook_signing_secret"

@app.post("/webhook/onlyfans")
async def handle_onlyfans_webhook(
    request: Request,
    signature: str = Header(None),  # header name: Signature
    x_ofapi_idempotency_key: str = Header(None),
):
    body = await request.body()
    computed_signature = hmac.new(
        SIGNING_SECRET.encode(),
        body,
        hashlib.sha256
    ).hexdigest()

    if not hmac.compare_digest(computed_signature, signature or ""):
        raise HTTPException(status_code=401, detail="Invalid HMAC signature")

    payload = json.loads(body)
    event_type = payload.get("event")

    # Deduplicate on x_ofapi_idempotency_key, then process asynchronously
    return {"status": "ok", "event": event_type}
```

## Automation & No-Code Pipelines

Rather than managing extensive custom server backends, growth and operations teams use [OnlyFansAPI](https://onlyfansapi.com/) to pipe events directly into standard automation platforms via native integrations.

### n8n Community Node Workflow

Using the official [OnlyFansAPI n8n Integration Quickstart](https://docs.onlyfansapi.com/integrations/n8n) package (`@onlyfansapi/n8n-nodes-onlyfansapi`), users can construct visual workflows:

1. **Trigger:** Set the OnlyFans API trigger to listen for `subscriptions.new`.
2. **Filter:** Route paid subscribers (`payload.replacePairs["{PRICE}"]` is not `"free"`) separately from those joining for free.
3. **Action:** Call an OpenAI or Claude API node to dynamically generate a custom welcome message.
4. **Output:** Use the `OnlyFans API` node's send message action to greet the subscriber within seconds.

### Zapier Instant Triggers

The [OnlyFansAPI Zapier App & Instant Triggers](https://docs.onlyfansapi.com/integrations/zapier) allow non-technical operators to build robust alert systems:

- **Trigger:** Instant trigger on Chat Message PPV Purchased (`messages.ppv.unlocked`). Zapier has no tip trigger, so route `tips.received` through a raw webhook instead.
- **Action 1:** Send a rich alert to a creator's dedicated Slack channel (e.g., `#vip-revenue-alerts`).
- **Action 2:** Log customer lifetime value (LTV) and tip records directly into a Google Sheets or Airtable CRM.

### Telegram & Slack Notification Bots

For agency chat teams managing multiple creators, pushing tip alerts directly to Telegram ensures immediate engagement with high-value VIPs. Call this from your `tips.received` handler with `payload["amountGross"]` as the amount and `payload["user"]["name"]` as the fan name:

```python
import requests

def send_telegram_alert(bot_token, chat_id, fan_name, tip_amount, creator_handle):
    message = (
        f"🚨 *NEW TIP RECEIVED*\n"
        f"• *Creator:* @{creator_handle}\n"
        f"• *Fan:* {fan_name}\n"
        f"• *Amount:* ${tip_amount:.2f}\n"
        f"⚡ *Action Required:* Prioritize reply in chat."
    )
    url = f"https://api.telegram.org/bot{bot_token}/sendMessage"
    requests.post(url, json={
        "chat_id": chat_id,
        "text": message,
        "parse_mode": "Markdown"
    })
```

## Webhook Delivery Resilience & Retry Architecture

High-volume creator accounts see bursts of interactions during mass DM drops or live streams. Robust webhook consumer implementations must adhere to strict reliability patterns to prevent data loss or duplicate message sending.

To ensure maximum uptime, development teams should implement these three foundational patterns:

1. **Immediate Acknowledgement (`2xx` Handshake):** A delivery succeeds only if your endpoint returns a `2xx` within the 10-second request timeout (5 seconds to establish the connection). Persist the payload, respond, and only then trigger lengthy operations such as LLM text generation or complex image processing.
2. **Idempotency Safeguards:** Delivery is at-least-once, not exactly-once. Consumers must store the `X-OFAPI-Idempotency-Key` header value (e.g. `evt_9c1f0a4b…`), which stays identical across every retry and manual redelivery of the same event, and skip events already processed—preventing duplicate actions such as sending two welcome DMs. Ephemeral events like `users.typing` carry no key.
3. **Retries & Recovery:** If your server returns any non-2xx status or times out, the dispatcher retries up to 3 attempts in total: immediately, about 10 seconds after the first failure, and about 100 seconds after the second. After that the delivery is abandoned (a `404` or `410` stops retries immediately). Delivery records are kept for 7 days and any of them can be replayed via the Redeliver Webhook Delivery endpoint. After 20 consecutive failed deliveries the webhook is paused, and events fired while it is paused are lost—backfill that window from the API.