OnlyFans Webhook Setup
This is a practical, step-by-step OnlyFans webhook setup guide using ofapis, so your app reacts to events in real time instead of polling on a timer. You'll register an endpoint, verify signatures, handle events safely, and confirm delivery. Budget about ten minutes.
Step 1 — Stand up an HTTPS endpoint
Webhooks require a public HTTPS URL that returns quickly. A minimal receiver:
from flask import Flask, request
app = Flask(__name__)
@app.post("/webhooks/ofapis")
def receive():
raw = request.get_data() # keep the RAW bytes
sig = request.headers.get("X-Ofapis-Signature", "")
if not verify(raw, sig, SIGNING_SECRET):
return "", 400
event = request.get_json()
enqueue(event) # do work async
return "", 200 # ack fast
Return 2xx fast and push the real work onto a queue — slow handlers get retried and can pile up.
Step 2 — Register the endpoint
In the dashboard, add the endpoint URL (HTTPS only) and subscribe it to events. message.received is live today. On creation you get a signing secret — a random base64url string — shown once; store it like any other credential. This is the same flow described in OnlyFans webhooks.
Step 3 — Verify the signature
Every request carries X-Ofapis-Signature: t=<timestamp>,v1=<hex>. Compute HMAC-SHA256 over <timestamp>.<raw_body> with your secret, compare in constant time, and reject stale timestamps to block replays:
import hmac, hashlib, time
def verify(raw, header, secret, tolerance=300):
parts = dict(p.split("=", 1) for p in header.split(","))
ts, v1 = parts["t"], parts["v1"]
if abs(time.time() - int(ts)) > tolerance:
return False
signed = f"{ts}.".encode() + raw
expected = hmac.new(secret.encode(), signed, hashlib.sha256).hexdigest()
return hmac.compare_digest(expected, v1)
Always verify against the raw body bytes, before any JSON parsing or framework re-serialization mutates them.
Step 4 — Handle events idempotently
Delivery is at-least-once, so a retry reuses the same event id. Deduplicate on it before acting:
def enqueue(event):
if seen(event["id"]): # already processed? drop it
return
if event["type"] == "message.received":
auto_reply(event["account_id"], event["data"])
The payload is a stable envelope — id, type, created, account_id, and a scoped data object. Pair message.received with the messaging API to auto-reply, or feed it into a chatbot.
Step 5 — Confirm and monitor
Send a test event and watch it land. The dashboard shows delivered, retrying, and failed counts per endpoint; failed deliveries are retried with exponential backoff, so a brief outage self-heals. Common gotchas:
| Symptom | Likely cause |
|---|---|
| Signature never matches | Verifying parsed JSON, not raw bytes |
| Random 400s after a while | Timestamp tolerance too tight / clock skew |
| Duplicate replies sent | No dedupe on event id |
| Deliveries marked failed | Handler too slow — ack first, work async |
Webhook deliveries don't cost credits; only your outbound API calls are metered. For the event catalog and payload details, see webhook events explained.
FAQ
What do I need before setting up an OnlyFans webhook?
A public HTTPS endpoint that returns 2xx quickly, a connected OnlyFans account, and the signing secret from the dashboard. That's enough to register an endpoint and start receiving message.received events.
How do I verify a webhook is really 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.
Why must I use the raw request body?
JSON parsing and framework re-serialization can reorder keys or change whitespace, which changes the bytes the signature was computed over. Verify against the untouched raw body first, then parse.
What happens if my endpoint is temporarily down?
ofapis retries failed deliveries with exponential backoff and shows retrying and failed counts in the dashboard. Because retries reuse the same event id, deduplicate so you don't process an event twice.
OnlyFans webhooks reference · Webhook events explained · Messaging API · Docs · Start free