How to Build an OnlyFans Bot
How to build an OnlyFans bot comes down to one loop: something happens, you react. This guide builds that loop end-to-end with ofapis — token, account connection, a signed webhook, and an auto-reply. ofapis is an independent API, not affiliated with OnlyFans; you automate your own connected account.
Step 1 — Get a token
Create an ofapis_sk_... key in the dashboard (start free) and keep it server-side. Confirm it works:
curl https://api.ofapis.com/api/public/v1/me \
-H "Authorization: Bearer ofapis_sk_..."
{ "workspace": "acme", "accounts": [{ "id": "42", "status": "active" }], "credits": { "remaining": 250 } }
Read accounts[].id — you scope calls to it. See authentication for the full model.
Step 2 — Connect the account
You connect your creator account once, in the dashboard. ofapis stores the session encrypted, runs a dedicated proxy per account, and keeps it alive with no refresh cron. Your bot never touches OnlyFans credentials — it only sends the Bearer token.
Step 3 — Register a webhook
Polling for new DMs wastes credits. Instead, have ofapis call you. Register an HTTPS endpoint and the message.received event via the webhooks API or dashboard. You get back a signing secret — store it. Each delivery is a signed JSON POST:
{
"id": "evt_7f31",
"type": "message.received",
"accountId": "42",
"data": { "chatId": "123", "from": { "username": "fan_jane" }, "text": "hey!" }
}
Step 4 — Verify and auto-reply
Verify the X-Ofapis-Signature HMAC over the raw body, respond 2xx fast, then reply. In Node:
import express from "express";
import crypto from "crypto";
const app = express();
const SECRET = process.env.OFAPIS_WEBHOOK_SECRET;
const TOKEN = process.env.OFAPIS_TOKEN;
function verify(raw, sig) {
const expected = crypto.createHmac("sha256", SECRET).update(raw).digest("hex");
return crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(sig));
}
app.post("/webhooks/ofapis", express.raw({ type: "*/*" }), async (req, res) => {
if (!verify(req.body, req.headers["x-ofapis-signature"] || "")) return res.status(401).end();
res.status(200).end(); // ack immediately
const evt = JSON.parse(req.body.toString());
if (evt.type !== "message.received") return;
await fetch(`https://api.ofapis.com/api/public/v1/chats/${evt.data.chatId}/messages`, {
method: "POST",
headers: { Authorization: `Bearer ${TOKEN}`, "Content-Type": "application/json" },
body: JSON.stringify({ text: "Thanks for the message — I'll reply shortly!" }),
});
});
app.listen(3000);
A 200 on the send means delivered and one credit spent; failed calls are not charged. See sending messages for media and pay-to-unlock options.
Step 5 — Make it idempotent
Webhook delivery is at-least-once, so a retry can arrive twice. Key on evt.id in a store (Redis, a DB unique index) and skip anything you've already handled — otherwise a network retry double-sends your reply.
Common errors
| Status | Meaning | Fix |
|---|---|---|
401 (webhook) |
Bad signature | Verify on raw bytes with the right secret |
401 (send) |
Revoked token | Reissue the key in the dashboard |
404 |
Stale chatId |
Fan unsubscribed; skip it |
429 |
Rate limit | Back off; check rpm on pricing |
FAQ
Do I have to poll for new messages?
No — register a webhook for message.received and ofapis POSTs to your endpoint when a fan writes. Polling burns credits and rate limit for mostly-empty responses.
How do I stop the bot double-replying?
Store each webhook's id and ignore duplicates. Deliveries are at-least-once, so idempotency on the event id is what keeps retries from sending twice.
Do incoming webhooks cost credits?
No. Credits are spent on your outbound calls. Inbound deliveries from ofapis are not metered — you only pay when your reply send succeeds.
Can one bot serve multiple creators?
Yes. On Pro and up, connect several accounts and route each event by its accountId to the correct scoped /accounts/{id}/... calls.