Revenue
Pull OnlyFans transactions from a REST endpoint with your API key, or receive them as signed webhooks. Both carry the same transaction shape.
Two ways to get transactions into your own system: pull them from a REST endpoint, or have them pushed to a webhook URL you set on the API Keys page. Both use the same transaction shape.
Pull
GET https://api3.betterfans.link/v1/revenue/transactions
x-service-api-key: <your API key>| Query | Meaning |
|---|---|
since | RFC 3339 instant. Return rows first seen at or after it. Default: the last 24 hours. |
cursor | nextCursor from a previous page. Continue where that page ended. |
limit | 1 to 1000. Default 500. |
account | OnlyFans user id. Restrict to one account the key may act as. Required for keys that cannot list their accounts. |
{
"transactions": [
{
"id": "1234567890",
"accountId": "55321703",
"fanId": "88112233",
"kind": "tips",
"description": "Tip",
"status": "done",
"grossCents": 1000,
"netCents": 800,
"feeCents": 200,
"vatCents": 0,
"currency": "USD",
"occurredAt": "2026-09-07T11:58:02Z",
"observedAt": "2026-09-07T11:58:40.120Z",
"deleted": false
}
],
"count": 1,
"hasMore": false,
"nextCursor": "",
"checkedAt": "2026-09-07T12:00:00.000Z"
}Rows are ordered by observedAt, the time the row was first seen or seen to
change. A transaction can appear more than once with the same id when its
status or amounts change, or when it is deleted. Treat the latest row for an
id as current.
To poll, store nextCursor and pass it back as cursor. When hasMore is
false you are caught up. Do not combine since and cursor.
| Status | Meaning |
|---|---|
| 401 | Missing or invalid key. |
| 403 | The key may not act as account. |
| 400 | Bad since or cursor, or the key needs account. |
Push
Agency owners and admins can set one webhook URL under API Keys. Every new or changed transaction for the agency's accounts is POSTed once as JSON:
{
"id": "e7c9d2d4-...",
"type": "transaction.observed",
"createdAt": "2026-09-07T11:58:41.003Z",
"data": { "...": "same fields as a pull row" }
}| Header | Meaning |
|---|---|
x-ofm-event | transaction.observed, or test for the Send test button. |
x-ofm-delivery | Unique id of this delivery. Use it to drop duplicates. |
x-ofm-signature | t=<unix seconds>,v1=<hex>, where hex is HMAC-SHA256 over <t>.<raw body> with your signing secret. |
Verify with the secret shown by Reveal secret:
import { createHmac, timingSafeEqual } from "node:crypto"
export function verify(rawBody: string, header: string, secret: string): boolean {
const parts = Object.fromEntries(header.split(",").map((kv) => kv.split("=")))
const expected = createHmac("sha256", secret).update(`${parts.t}.${rawBody}`).digest("hex")
const fresh = Math.abs(Date.now() / 1000 - Number(parts.t)) < 300
return fresh && timingSafeEqual(Buffer.from(expected), Buffer.from(parts.v1 ?? ""))
}Respond with any 2xx within 10 seconds. Anything else is retried four more times over about three hours, then the delivery is marked failed. The card shows the endpoint as failing until a delivery succeeds again. The URL must be public https.
Earnings
Access OnlyFans revenue data programmatically — earnings charts, payout history, transaction records, referral balances, and campaign analytics via the BetterFans Link SDK, the infrastructure behind OFManager.
Types
TypeScript types for the OnlyFans API — User, Message, Post, Media, Subscription, and all entity types used across the BetterFans Link SDK, the infrastructure behind OFManager.