X-Fanvue-Signature header. By the end of this page your endpoint checks it on every request and rejects anything that fails, with a complete Express receiver you can copy.
You need the signing secret for your destination and access to the raw request body in your framework. The secret depends on how you registered the endpoint, so start with Get your signing secret.
The signature header
During a signing-secret rotation the header carries two MACs:
t=<timestamp>,v0=<old>,<new>. The second MAC arrives as a bare field with no =, continuing the v0 list. Compare against every MAC and accept when any one matches. If you read only the first, you reject every delivery signed with the newer secret for the length of the overlap.
Deliveries also carry an X-Fanvue-Topic header. The signature covers <t>.<raw body> only, so that header is unauthenticated. Derive the topic from the signed body (type on the envelope, field presence on legacy events), never from the header.
Get your signing secret
Which secret signs your deliveries depends on how the webhook was registered. The two paths don’t share a secret.
For the Events tab and the manifest, open your app’s Events tab in the Developer Area and click View signing secret. Keep the secret server-side in an environment variable such as
FANVUE_WEBHOOK_SECRET, and never ship it in client-side code or commit it to source control. Security covers secret storage and rotation for the rest of your app.
A subscription created with POST /webhooks/subscriptions gets its own secret, returned as signingSecret and shaped whsec_ followed by 64 hex characters:
How verification works
1
Parse the header
Split
X-Fanvue-Signature on the comma. Read t and collect every v0 value, including bare fields that follow a v0= field. Reject the request when t is missing, repeated or not a plain decimal number, or when no MAC is present.2
Reconstruct the signed payload
Concatenate
t, a period and the raw request body: {timestamp}.{body}. Use the exact bytes Fanvue sent. Parsing the JSON and re-serialising it changes the bytes, and the signature won’t match.3
Compute the expected signature
Run HMAC-SHA256 over the signed payload with your signing secret and hex-encode the result.
4
Compare in constant time
Compare the expected signature against each MAC with a timing-safe comparison (
crypto.timingSafeEqual in Node, hmac.compare_digest in Python). Accept when any MAC matches. A plain == leaks the secret through timing differences.5
Reject stale timestamps
Reject the request when
t is more than 300 seconds from the current time. The timestamp is inside the signed content, so a captured delivery cannot have its t refreshed.Code samples
Both samples take the raw request body and theX-Fanvue-Signature header value and return whether the request is authentic.
- Node.js
- Python
verifyWebhookHmac with fanvueSignatureContract(), and parseFanvueSignatureHeader implements the same parse rules. See SDK webhooks.
When verification fails, respond 401 and don’t process the payload. When it succeeds, persist the event and return 2xx before you process it, as described in Delivery, retries and idempotency.
Complete receiver
This Express receiver answers the probe, verifies every other request against the raw body and accepts a second MAC during secret rotation. Answer2xx to the unsigned verification probe before you check signatures, as it does, or POST /webhooks/subscriptions fails.