REST API
Webhooks
Receive sales, emergencies and AI pauses live — events, HMAC signature, deliveries.
Receive events without polling the API: POST /public/v1/webhooks with a public https:// address (port 443 or 8443) and the events you want. The response gives the signing secret whsec_… only once.
Permissions: webhooks:write (and webhooks:read to list and read the log), plus the read permission of each event.
| Event | When | Read permission required |
|---|---|---|
sale.created | a sale (MYM, Telegram, OnlyFans, Fanvue) | sales:read (+ telegram:read for Telegram) |
emergency.created | a new emergency | notifications:read |
ai.paused | the AI paused on a fan, or turned off for a creator | chatting:read |
ping | POST /public/v1/webhooks/{id}/ping, to test | — |
An event only carries identifiers and amounts, never a fan's name or a message: read the details with your key.
{
"id": "evt_9b1c…",
"type": "sale.created",
"apiVersion": "v1-beta",
"createdAt": "2026-11-07T12:30:00Z",
"data": {"platform": "mym", "creatorId": "emma-demo", "saleId": "s_…",
"conversationId": "123", "amountMinor": 1500, "currency": "EUR", "at": "…"}
}Verify the signature
Header FanConvert-Signature: t=<seconds>,v1=<hex>: compute HMAC-SHA256 of "<t>.<raw body>" with your secret, compare in constant time, reject a t older than 5 minutes.
import hashlib, hmac, time
def verify(secret: str, header: str, body: bytes) -> bool:
fields = dict(p.split("=", 1) for p in header.split(","))
if abs(time.time() - int(fields["t"])) > 300:
return False
expected = hmac.new(secret.encode(), f"{fields['t']}.".encode() + body,
hashlib.sha256).hexdigest()
return hmac.compare_digest(expected, fields["v1"])Delivery
- Answer
2xxwithin 10 s. Otherwise, retries after 1 min, 5 min, 30 min, 2 h and 6 h (six attempts), thenfailed. - Redirects are never followed.
- The
FanConvert-Event-Idheader lets you ignore a duplicate. - The log:
GET /public/v1/webhooks/{id}/deliveries. - A webhook lives with its key: key revoked or expired, it is disabled.
DELETE /public/v1/webhooks/{id}disables it (the log stays). - Events are detected about every 30 s; on creation, nothing from the past is replayed.
Reference: Webhooks.
Last updated on