Webhooks
Configure and verify the implemented outbound webhook deliveries.
Create an endpoint
PROOpen Webhooks in the dashboard. Outbound webhooks require the canUseOutboundWebhooks entitlement, enabled for Pro and Pro+, and the global feature flag. The creation service returns a signing secret, but the current dashboard does not display it and endpoint listing does not return it.
The service matches an event name or *. The dashboard currently subscribes new endpoints to member.created, front.switched and note.created; their production emitters are not implemented. This configuration is managed through the authenticated dashboard, not a public v1 API-key route.
Use the verified test event
The dashboard test action sends this envelope, with a generated delivery identifier:
{
"eventType": "webhook.test",
"payload": {
"test": true
},
"deliveryId": "11111111-1111-4111-8111-111111111111"
}Verify the raw request body
Deliveries use POST with JSON. X-Florality-Timestamp is a Unix timestamp in milliseconds. X-Florality-Signature is the hexadecimal HMAC-SHA256 of the timestamp, a full stop and the exact JSON body, using the endpoint’s signing secret.
The missing dashboard secret display prevents completing this verification setup through the UI alone. Once the secret is securely available, verify before parsing the body. This Node.js example also applies a receiver-chosen five-minute replay window. Store processed delivery IDs to avoid processing a retry twice.
import { createHmac, timingSafeEqual } from 'node:crypto';
// Call with the untouched request body (Buffer) and request headers.
export function verifyWebhook(rawBody, headers, secret, now = Date.now()) {
const timestamp = headers.get('x-florality-timestamp') ?? '';
const signature = headers.get('x-florality-signature') ?? '';
if (!/^\d+$/.test(timestamp) || !/^[a-f0-9]{64}$/.test(signature)) return false;
// Five minutes is this receiver's replay policy, not a Florality guarantee.
if (Math.abs(now - Number(timestamp)) > 5 * 60 * 1000) return false;
const expected = createHmac('sha256', secret)
.update(timestamp + '.')
.update(rawBody)
.digest();
return timingSafeEqual(expected, Buffer.from(signature, 'hex'));
}
// Pass process.env.FLORALITY_WEBHOOK_SECRET as secret.
// After verification, parse the JSON and deduplicate by deliveryId.Return a success status and handle retries
Any 2xx response marks delivery successful. Non-2xx responses and request failures retry, with a 15-second timeout per attempt. There are at most five attempts: the first delivery and four retries scheduled after 1 minute, 5 minutes, 30 minutes and 2 hours respectively, measured from the preceding failed attempt.
The retry worker processes due deliveries in batches of up to 100. Actual dispatch depends on the worker being invoked; the checked-in Kubernetes, Docker and Vercel configurations schedule it every five minutes. Live deployment scheduling has not been verified. Retries retain the delivery ID and use a fresh signature timestamp. Disabled or missing endpoints are marked failed by the worker.
The delay array also contains 12 hours, but the five-attempt cap means that delay is not used. The dashboard test sends its first attempt immediately.
