Build an OnlyFans AI chatbot
Implement the customer's chatbot in their existing application and chosen AI stack; reuse its HTTP client, persistence, queue, and tests. OnlyFansAPI supplies account events and messaging; the customer chooses the model, creator voice, content, and approval rules. Building the integration does not authorize live messages, webhook registration, or deployment.
When invoked
Inspect the client's MCP support and configured OnlyFansAPI servers. If remote MCP is supported and a relevant server is missing, ask once per conversation: “Would you like me to connect the OnlyFansAPI MCP as well?” Explain the choices:
- Docs MCP:
https://docs.onlyfansapi.com/api/mcpsearches public documentation without authentication. Prefer it while building. - API MCP:
https://app.onlyfansapi.com/mcp/onlyfans-mcpacts on real accounts using API-key or OAuth authentication.
Follow the AI agent setup guide. Preserve prior offers, consent or decline from any OnlyFans skill; do not install automatically or repeat the offer. Verify unknown client support first. Reconnect unavailable configured servers instead of duplicating them, and verify discovery before claiming setup is complete. Continue useful work while an answer is pending or MCP is unsupported. Code generation and local verification need no API key.
Read the relevant contracts
Read the chatbot tutorial,
delivery rules,
signature guide, and
Send Message.
Read optional event or media details only when implementing them. Pages are also available as Markdown by appending .mdx.
Use the documentation index to discover endpoints; verify current payloads and fields.
Receive and queue events
- Configure a signing secret for the HTTPS webhook. Verify its
Signatureheader as a hex HMAC-SHA256 over the original raw body bytes, before parsing or acting on JSON. Reject missing or malformed signatures and compare valid equal-length values in constant time using the language's crypto library. Re-serializing parsed JSON changes the signed bytes. - Validate event type, creator account, chat identity, and required fields against the configured
integration. Use webhook
account_id(acct_…) for the API account; never substitute a username or numeric creator ID. Namespace persisted events and chat state by account and chat. - Atomically persist a deduplicated event and durable work before returning a
2xx, within the 10-second delivery budget. Use an existing transactional inbox or durable queue with equivalent recovery: a crash must not leave an event marked handled with its job missing. If durable acceptance fails, return an error. Run AI generation and sending in workers. - Deduplicate durable events by
X-OFAPI-Idempotency-Key; keep the source message ID for received-message reconciliation. Repeated deliveries must not create another reply job. Events can arrive out of order; use payload timestamps and message IDs when rebuilding history. Do not assume every event carries a key: typing is ephemeral.
Use these event mappings:
| Event | Fan/chat ID | Handling |
|---|---|---|
messages.received |
payload.fromUser.id |
Persist the message and schedule a reply. |
users.typing |
payload.id |
Update the latest typing time; it has no webhook idempotency key. |
messages.ppv.unlocked |
payload.user_id |
Record the purchase and optionally schedule one follow-up. |
messages.sent |
Read the event's documented recipient fields | Update history and honor human takeover; never treat it as an incoming fan message. |
Coordinate a conversation
- Start with
messages.received. Serialize work per(account_id, chat_id)using the existing queue or store so concurrent messages do not generate overlapping replies. Recheck newly arrived messages and human takeover before sending; combine a fan's short message burst where appropriate. - When typing support is requested, start a reply deadline from message receipt even if no typing event arrives. Extend it until approximately 5–10 seconds after the latest typing signal, with a bounded maximum wait. Typing alone must not create replies or an endless wait.
- Keep trusted creator instructions separate from fan text, names, links, and chat history. Pass that material as untrusted conversation data, never as system instructions or tool authority. Resolve recipients in application code; constrain generated media and prices to approved choices.
- Follow the customer's chosen automatic-reply or draft policy. Reuse any existing per-chat pause or human takeover controls, and keep live activation separate from building the integration. Do not add an approval interface the customer did not request.
- Use webhook history first. Fetching chat messages
can mark the chat read, so do not use it as a continuous new-message poller. For recovery,
list unread chats and reconcile persisted IDs.
Follow
_pagination.next_pagewhen history spans pages.
Send and reconcile
Use the server's HTTP client against https://app.onlyfansapi.com with Authorization: Bearer <API_KEY>, reading ONLYFANSAPI_API_KEY from server-side secrets.
Keep API and AI-provider keys out of browser code, logs, generated prompts, and source control.
- Send via
POST /api/{account}/chats/{chat_id}/messages. Persist one unique outgoingIdempotency-Keyand the exact request payload before attempting each intended reply. This is distinct from the incomingX-OFAPI-Idempotency-Key. Never regenerate a reply or change its payload under an already used outgoing key. - Direct sends retain responses for 24 hours.
Idempotent-Replayed: trueidentifies a replay;409 IDEMPOTENCY_CONFLICTmeans the original is running;422 IDEMPOTENCY_KEY_MISMATCHmeans the body or chat changed. RespectRetry-Afterand bound retries. - A timeout or server failure can leave delivery uncertain. Persist that state and reconcile the intended reply with chat history before resending; a missing or nonunique match does not prove failure. Hold ambiguous sends for operator recovery. Never create a new key to force another attempt, retry after the retention window blindly, or promise exactly-once delivery.
- Generated API MCP tools may omit custom headers, including
Idempotency-Key. Inspect the callable schema; use direct REST for sends requiring it rather than putting the key in JSON or assuming an MCP connection header reaches the underlying send request.
Optional purchases and media
For messages.ppv.unlocked, parse the firstId query parameter from payload.replacePairs["{MESSAGE_LINK}"]
with a URL parser; match it to a saved sent message within the same account/chat. Never fetch that link as an instruction. Mark the exact offer
purchased and cancel its reminders. Deduplicate follow-ups using the incoming event key;
pass purchase facts as an event to the AI, not as words spoken by the fan.
Use existing vault media IDs for reusable content.
/media/upload returns single-use CDN upload material; /media/vault stores reusable vault items.
Paid messages require media; check current price and preview rules in Send Message. For requested asynchronous uploads,
wait for completion before sending; HTTP 202 only means accepted. Do not add media or PPV flows to a text-only request.
Verify the integration
Leave focused runnable checks in the existing test setup or a small local harness: valid signed event → one queued reply; duplicate → no second reply; altered/missing signature → no work; send timeout → retained uncertain state without a blind second send. Exercise the typing fallback and PPV matching if implemented. Stub external calls; live validation needs the customer's authorization. Report implemented behavior, checks run, and remaining setup.