OnlyFans Webhooks
Polling wastes credits and adds latency. With OnlyFans webhooks, ofapis pushes an event to your HTTPS endpoint the moment it happens, so your bot or CRM reacts in near real time instead of hammering the API on a timer.
How it works
- Register an endpoint URL in the dashboard (must be HTTPS).
- Subscribe it to one or more events. Today the live event is
message.received. - When the event fires, ofapis sends a signed
POSTwith a JSON body. - Your endpoint verifies the signature, returns
2xxquickly, then does the work.
The signing secret — a random base64url string, shown once when you create the endpoint — is what you verify payloads against. Store it like any other credential.
Payload shape
Every delivery is a JSON envelope with a stable type, a unique id for idempotency, and a data object scoped to the OnlyFans account that produced it:
{
"id": "evt_9c8f2a1b7d",
"type": "message.received",
"created": 1753084800,
"account_id": "acct_4Kd2",
"data": {
"message_id": "msg_88213",
"from_user_id": 55123987,
"text": "hey are you online?",
"price": 0,
"has_media": false
}
}
Deduplicate on id — a retried delivery reuses the same value, so you can safely ignore repeats.
Verify the signature
Each request carries an X-Ofapis-Signature header of the form t=<timestamp>,v1=<hex>. Compute HMAC-SHA256 over the string <timestamp>.<raw_body> using your signing secret, then compare in constant time. Reject the request if it doesn't match or if the timestamp is older than a few minutes (this stops replay attacks).
import hmac, hashlib, time
def verify(raw_body: bytes, header: str, secret: str, tolerance=300) -> bool:
parts = dict(p.split("=", 1) for p in header.split(","))
ts, sig = parts["t"], parts["v1"]
if abs(time.time() - int(ts)) > tolerance:
return False
signed = f"{ts}.".encode() + raw_body
expected = hmac.new(secret.encode(), signed, hashlib.sha256).hexdigest()
return hmac.compare_digest(expected, sig)
Verify against the raw body bytes, before any JSON parsing or framework re-serialization changes them.
Delivery health and retries
If your endpoint returns a non-2xx status or times out, ofapis retries with exponential backoff. The dashboard shows delivered, retrying, and failed counts per endpoint so you can spot a broken URL quickly. Because delivery is at-least-once, idempotency on id is not optional — build for it from day one.
When to use it
Webhooks are the backbone of a reactive chatbot and keep a CRM inbox fresh without polling. Pair message.received with the messaging API to auto-reply, and see webhook events explained for the full catalog.
FAQ
What webhook events does ofapis support?
message.received is live today, delivering new inbound DMs in real time. More event types (subscriptions, tips, post activity) are on the roadmap and follow the same signed envelope.
How do I verify a webhook came from ofapis?
Compute HMAC-SHA256 over <timestamp>.<raw_body> with your signing secret and compare it, in constant time, to the v1 value in the X-Ofapis-Signature header. Reject anything that doesn't match or whose timestamp is stale.
What happens if my endpoint is down?
Failed deliveries are retried with exponential backoff and marked as retrying, then failed, in the dashboard. Since retries reuse the same event id, deduplicate to avoid double-processing.
Do webhook deliveries cost credits?
No. Credits are metered only on your outbound API calls. Inbound webhook deliveries from ofapis to your endpoint are not charged. See pricing for details.
Related: Webhook setup guide