OnlyFans Auto Reply Bot
Most of what lands in a creator's inbox is not a conversation. It is "hi", "you there?", "how much for a custom", and the same three questions at 2am. An auto reply bot that handles exactly those — deterministically, with no model in the loop — is cheaper to run, impossible to hallucinate with, and testable in CI. This page is that bot: the trigger table, the matcher, the guards, and the line past which it must stop and fetch a person. The endpoints underneath it — the signed delivery, the context read, the send — are on the chatbot API page.
Rules first, model later
A rules bot is worth building before an LLM bot for a reason you can measure: every reply it sends is one you wrote and approved in advance. The questions it cannot answer are the ones worth paying inference for, and after a fortnight of logging you will know exactly which those are instead of guessing.
Start by instrumenting, not replying. Run the matcher in shadow mode for a week — log which rule would have fired on each inbound message and send nothing. The rules with high hit counts and no ambiguity go live first; the long tail goes to a human and, later, to a model.
The trigger table
Keep rules as data, not code. Ordered, first match wins, each one carrying its own cooldown and whether it is allowed to fire unattended:
[
{ "id": "away", "when": "outside_hours", "cooldown_s": 21600,
"text": "I'm asleep 💤 back around 9am — leave it here and I'll answer first thing." },
{ "id": "first_touch","when": "thread_is_new", "cooldown_s": 0,
"text": "hey {name}! thanks for subscribing — what are you into?" },
{ "id": "price", "when": "matches:/\\b(price|how much|cost)\\b/i", "cooldown_s": 3600,
"text": "menu's pinned at the top of my page — anything on it you want, just say the word" },
{ "id": "greeting", "when": "matches:/^(hi|hey|hello|yo)[!. ]*$/i", "cooldown_s": 1800,
"text": "hey you 😊 what are you up to?" },
{ "id": "escalate", "when": "matches:/\\b(refund|charge ?back|custom|meet|real person)\\b/i",
"handoff": true }
]
Three properties make this maintainable. Rules are ordered, so escalate sitting last still wins nothing it should not — put it first if it must pre-empt. cooldown_s is per fan per rule, which is what stops a fan who types "hi" four times getting four identical greetings. And a rule with handoff: true sends nothing at all; it flags the thread.
first_touch is the odd one out, because it fires on the fan's first message, not on the subscription. Nothing pushes a new subscriber, so reaching people who subscribe and never write takes the polling loop described under welcome message automation.
The matcher
What follows is only the decision and the send; the receiver that verifies the signature and hands events to this function is on the Node.js webhook receiver page.
const BASE = "https://api.ofapis.com/api/public/v1";
function pick(rules, ctx) {
for (const rule of rules) {
if (!test(rule.when, ctx)) continue;
if (ctx.firedAt(rule.id) + rule.cooldown_s * 1000 > Date.now()) continue;
return rule;
}
return null;
}
async function respond(event, accountId, token) {
const { chatId, message } = event.data;
if (message.fromUser.id === ctx.creatorId) return; // never answer yourself
if (ctx.autoRepliesInThread(chatId) >= 4) return flag(chatId, "cap");
const rule = pick(RULES, ctx.for(chatId, message));
if (!rule) return flag(chatId, "unmatched");
if (rule.handoff) return flag(chatId, rule.id);
const text = rule.text.replace("{name}", ctx.displayName(chatId) || "you");
const res = await fetch(`${BASE}/accounts/${accountId}/chats/${chatId}/messages`, {
method: "POST",
headers: {
"Authorization": `Bearer ${token}`,
"Content-Type": "application/json",
"Idempotency-Key": `auto-${message.id}`,
},
body: JSON.stringify({ text }),
});
if (res.status === 429) return retryAfter(res.headers.get("Retry-After"));
ctx.recordFire(rule.id, chatId);
}
Note what is absent: no context read. A rules bot decides from the webhook payload alone, so a reply is one billed call instead of two — two credits for the account-scoped send, against three for a read-then-send turn. With a per-token ceiling of 60, 100 or 500 requests a minute by plan, that single call is also what lets a rules bot clear an inbox backlog at full rate while an LLM bot is still waiting on generation. Pacing and the Retry-After contract are on the rate limits page.
Templating is entirely yours. The API takes a finished text field and does nothing with {name} — substitute before you send. Know where that name comes from, though: the webhook body gives each user as {id, _view} with no handle and no display name, so {name} means either your own store or a GET /accounts/{id}/users/{targetUserId} lookup for one more credit. Fall back to a neutral word when you have neither, because Hey undefined! is worse than no personalisation at all.
Away hours, properly
"Outside hours" is a timezone question, and the creator's timezone is not your server's. Store an IANA zone per account and evaluate against that. Two refinements worth the effort:
An away reply should fire once per quiet period, not once per message — the 6-hour cooldown in the table above is doing that job. And rather than leaving the thread cold, queue a real follow-up for the morning with POST /accounts/{id}/scheduled/messages — 3 credits, the heaviest call in this loop — so the fan gets an actual answer at 9am instead of a second apology; the scheduling tool page covers that endpoint's shape.
Where the bot must stop
The handoff list is the most valuable part of the config, because every item on it is a way an automated reply costs real money:
- Anything about money already spent — refunds, chargebacks, "I paid and didn't get it". A wrong answer here becomes a dispute.
- Price negotiation. A canned menu reply is fine; haggling is not, and a bot that concedes once sets the price for that fan forever.
- Custom content requests. These are a commitment to deliver, with a deadline.
- Tips and unusually large purchases. A fan who just sent money is the one most worth a human minute; detecting these means polling, since no webhook fires for them, as the tips page explains.
- "Are you a real person?" Answer it with a person.
- Distress of any kind. No template is appropriate.
Mechanically a handoff is cheap: mark the thread, stop auto-replying to it until a human clears the flag, and surface it in whatever queue your chatters work. What matters is that the flag is sticky — a thread that escalated once should not silently fall back to canned replies two messages later.
Guards that prevent the embarrassing failures
Skip events where fromUser.id is the creator, or the bot answers itself and the loop never stops. Cap auto-replies per thread — four in a row without a human is a good default — so a fan who keeps typing eventually reaches a person instead of a wall of templates. Deduplicate on X-Ofapis-Delivery, because webhook delivery is at-least-once and a retried event will otherwise fire the same rule twice. And send with an Idempotency-Key keyed on the inbound message id so a timed-out POST can be retried without double-messaging.
Track three numbers weekly: coverage (share of inbound that matched a rule), handoff rate, and median time to first reply. Coverage rising with a flat handoff rate means the rules are working. Coverage rising with handoffs rising means a rule is matching things it should not.
The first concrete step is the endpoint, not the rules: register the URL and store the signing secret following the webhook setup guide, then run the matcher in shadow mode against real deliveries for a week before it is allowed to send anything.
FAQ
Do I need an LLM for a useful auto reply bot?
No, and the first version is usually better without one. Greetings, away hours, menu questions and first-touch messages are all deterministic, and handling them with a rule table means every outgoing line was written and approved by a human in advance.
How do I stop a fan getting the same canned line repeatedly?
Give each rule a per-fan cooldown and record the last fire time per fan and rule. A greeting rule with a 30-minute cooldown answers the first "hey" and ignores the next three, which is what a person would do anyway.
What does a rules bot cost to run?
Two credits per reply it actually sends on the account-scoped path, and nothing for the inbound delivery — it answers from the webhook payload without reading history first. Failed sends are not all free, though: 401, 402, 403 and 429 are rejected before any debit, 5xx, 424 and 409 are refunded, but a 400 on a bad body or a 422 from a dead session is debited and stays debited. Budget on replies delivered plus a small allowance for errors.
Should the away message be the end of the conversation?
No. Pair it with a scheduled message for the morning so the fan gets a real answer at a reasonable hour; the away reply only buys time. A thread that gets an apology and nothing else is a thread that goes quiet.
How do I hand a thread to a human mid-conversation?
Mark the thread flagged in your own store, stop matching rules against it, and show it in the chatter queue. Keep the flag sticky until a person clears it — the messaging API does not track ownership, so handoff state lives entirely in your application.