ofapisv1
guide

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.

Next steps

Related guides

All guides
Start free — 250 credits, no card
Generate a token and make your first call in minutes.
Get started