OnlyFans Welcome Message Automation
If you are building OnlyFans welcome message automation for one creator or a roster of them, the send is the easy half: one call to the chat endpoint, 2 credits. The hard half is detecting the new subscriber cheaply enough to poll all day, and keeping a ledger tight enough that nobody is welcomed twice. This page covers both, with the arithmetic. It is the smallest automation worth building and usually the first to pay for itself; the full endpoint reference covers everything around it.
Detection is the whole problem
No subscription webhook exists. message.received is the only event type delivered, so a new subscriber who has not written to you produces no push at all. Detection means polling, and the two ways to poll differ by an order of magnitude in cost.
The notification feed. GET /accounts/{id}/notifications?type=subscribed&limit=50 returns the subscription events directly — one credit per call, regardless of how many subscribers you have. This is the right primary signal; the notifications API reference covers the other type filters and the unread counter that sits beside the feed. Two constraints: it takes type and limit (clamped 1–100) but no offset, so you cannot page backwards through history; and the item payload is forwarded verbatim from OnlyFans, so read what your own account actually returns rather than coding against a shape you assumed. Treat an unexpected field as missing, not as a crash.
The subscriber diff. GET /accounts/{id}/subscribers?type=active&limit=100 paginates by offset, so a full snapshot of a 5,000-fan audience is 50 pages and 50 credits. Diffing that every fifteen minutes is 4,800 credits a day — more than a month's Starter allowance in a week. Run it nightly as a reconciliation pass that catches anything the feed missed, never as the primary loop.
Polling cadence is a budget decision
Your poll interval sets both the fan's wait and your monthly bill, and the arithmetic is unforgiving at one credit per check:
| Interval | Calls/month | Median wait | Worst-case wait |
|---|---|---|---|
| 1 min | ~43,800 | 30 s | 60 s |
| 5 min | ~8,760 | 2.5 min | 5 min |
| 15 min | ~2,920 | 7.5 min | 15 min |
Five minutes is the usual compromise: a median wait of two and a half minutes, at 8,760 credits — about 29% of a Starter month for a single creator. One-minute polling exceeds the whole Starter allowance before a single welcome is sent. For a roster, multiply by the number of accounts — ten creators at five minutes is 87,600 credits a month, which only fits on Pro. Allowances are on pricing, and the per-token request ceiling on the rate limits page is the other constraint when you stagger a roster's polls.
Sending it
The chat is keyed by the fan's own user id, so a brand-new subscriber's chatId is that id — no lookup needed to find the thread. One send on the account-scoped path is weighted at 2 credits, not 1 — twice what a read of the same thread costs, and the one charge per welcomed fan you cannot optimise away:
curl -X POST https://api.ofapis.com/api/public/v1/accounts/42/chats/78910/messages \
-H "Authorization: Bearer ofapis_sk_..." \
-H "Idempotency-Key: welcome-42-78910" \
-H "Content-Type: application/json" \
-d '{"text":"hey Sam! thanks for subscribing 💕 tell me what you came here for",
"mediaFiles":[777888999],
"price":0,
"lockedText":false}'
A gotcha that costs an afternoon: this endpoint takes price in cents, while a mailing takes priceCents. They are different objects with different field names and no error will tell you which you got wrong — a welcome sent with the wrong key goes out free.
The Idempotency-Key is doing real work here. Derive it from the account and fan id, never from a timestamp, so that every retry path in your system collapses onto the same key. A repeat replays the original response with Idempotency-Replayed: true and is not charged; a concurrent repeat gets 409 IDEMPOTENCY_CONFLICT. That is your second line of defence behind the ledger — and the ledger is the first.
The ledger, and why re-subscribers break it
Store a welcomed(account_id, fan_id, sent_at, variant) row and check it before every send. Not the last snapshot, not a cache — a durable row. Three situations make this mandatory:
- Overlapping polls. A slow run still in flight when the next one starts will see the same notification twice.
- Re-subscribers. A fan who lapsed and came back appears as new. Decide deliberately: a second welcome after six months is fine, a second welcome after six days reads as a bot. A
sent_atcomparison makes that one line of policy. - Replays after a deploy. Whatever cursor you keep, assume it will be reset by a bad migration at least once.
Personalisation that cannot embarrass you
Use the fan's display name if the feed gave you one, and otherwise fall back to a neutral opener rather than paying a credit for GET /accounts/{id}/users/{targetUserId} on every single new fan — that lookup takes the per-welcome cost from 2 credits to 3 for the sake of one word. Trim the name, cap its length, and reject anything that looks like markup; display names are user-supplied and some are deliberately hostile to templates.
Leading with media is common: upload once to the vault, then reference the ids on the message. Set price above zero with lockedText: true and the welcome becomes a first-touch PPV — worth A/B testing against a free teaser rather than assuming, since a paywall on message one also suppresses the reply you are trying to provoke. The vault media and PPV automation pages cover the attachment side.
Measuring it for free
The expensive way to measure a welcome is to re-read each thread a day later at one credit per fan. Do not. If you already run a webhook endpoint, replies arrive as message.received events at no cost — match the inbound chatId against your welcomed table within a 24-hour window and you have a reply rate per variant with zero extra API spend. The same endpoint can answer those replies: an auto reply bot with a rule table is the usual next build after this one.
Record the variant on the ledger row from day one, even if you only ship one message. Retrofitting that column later means throwing away the history you actually wanted to compare against. Three things worth watching: reply rate within 24 hours, median time from subscription to send (your poll interval is the floor), and what share of welcomed fans are still subscribed a month on. Tagging repliers into a fan list you can target later as they come in gives later campaigns a warm segment that cost nothing to build.
Where this stops: there is no subscription event to subscribe to, so a sub-minute welcome is not something this API can give you at a sane price, and nothing here reports what a fan has spent — if your pitch is "instant, personalised by spend tier", half of it has to come from data you keep yourself.
The next step that follows from this page: run the five-minute notification poll for a single creator, write the ledger row before the send rather than after, and watch the actual credit burn for a week before you add accounts. One creator at five minutes plus the sends is well inside a Starter month. Create an API key · per-token rate limits
FAQ
Is there a new-subscriber webhook?
No. message.received is the only event type that exists, so a subscription produces no push and detection has to be polled. The cheap way is the notification feed filtered to type=subscribed, which is one credit per check no matter how large the audience.
Why not just diff the subscribers list?
Because it scales with audience size rather than with new signups. A 5,000-fan roster is 50 pages at 100 per page, so one snapshot costs 50 credits — fine as a nightly reconciliation, ruinous as a five-minute loop.
How fast should the welcome go out?
Fast enough that the fan is still around, which in practice means a poll interval of about five minutes and a median wait of two and a half. Going to one-minute polling cuts the wait by two minutes and multiplies your detection cost by five, which is rarely the better trade.
What stops a returning fan getting welcomed twice?
A durable ledger keyed on account and fan id, checked before every send, plus an Idempotency-Key built from the same two ids. Then add an explicit policy on the gap: re-welcoming after six months is reasonable, after six days is not, and the ledger's sent_at is what decides.
Should the welcome message be paid?
Test it rather than assume. A locked first message earns from the fans who convert immediately but suppresses replies from everyone else, and the reply is what makes the next three messages possible. Store the variant on the ledger row and compare reply rates before committing.