Scopes
Events
Webhook subscriptions validate event names against the backend event registry. Unknown names are rejected on create and update.System
Members
Conversations
Bookings and calendar
Finance
Products and credits
Forms
Progress
Documents
Payload contract
Every outbound delivery body has the same top-level shape:data is intentionally compact. It includes ids, statuses, timestamps, and
changed field names where relevant. It does not include raw model documents.
When an integration needs full detail, call the relevant scoped Public API read
endpoint with the ids from data.
Endpoints
Write requests require
Idempotency-Key.
Target URL validation
Webhook targets must be public HTTPS URLs. The API rejects invalid URLs, non-HTTPS URLs, local-only hostnames, private IPv4 ranges, loopback addresses, link-local addresses, and private IPv6 addresses.Request validation
The authenticated company is always derived from the Bearer token. Request
payloads never accept
companyId, createdByCoachId, secret hashes, retry
counters, delivery timestamps, or signing-secret metadata.
Subscription response fields
List responses:
Single subscription responses from create, get, update, and delete:
Create a webhook
data.subscription.signingSecret exactly once.
Store it securely. Later list and detail responses only return secret metadata:
prefix, last4, and rotatedAt.
Update, delete, and test responses
PATCH /public/v1/webhooks/{id} and DELETE /public/v1/webhooks/{id} return
data.subscription.id, name, status, events[], target.type,
target.url, secret.prefix, secret.last4, secret.rotatedAt,
lastDeliveryAt, lastSuccessAt, lastFailureAt, createdAt, and
updatedAt. Delete is a soft delete: it sets
data.subscription.status = deleted and removes the subscription from future
list/get responses.
POST /public/v1/webhooks/{id}/test queues a subscription.test event only
for an active subscription.
Test response fields:
Signing headers
FITsociety sends each outbound delivery as an HTTPPOST with JSON body and
these headers:
The signature uses the one-time signing secret and this signed string:
Verifying webhook signatures
Verify every delivery before processing it:- Read
X-FITsociety-Webhook-Timestamp,X-FITsociety-Webhook-Delivery, andX-FITsociety-Webhook-Signature. The event id is theidfield of the JSON body; it is not sent as a header. - Reject stale timestamps. The examples below allow 5 minutes of clock skew.
- Canonicalize the parsed JSON body by recursively sorting object keys while keeping array order. Do not sign the raw request bytes: FITsociety signs the canonical form, and the transmitted JSON is not key-sorted.
- Rebuild the signed string
<timestamp>.<deliveryId>.<eventId>.<canonical_payload_json>, computeHMAC-SHA256with your stored signing secret, prefix the hex digest withv1=, and compare against the signature header in constant time.
stableJson helpers below reproduce the canonicalization FITsociety uses
when signing. Payload values are object ids, statuses, ISO timestamps,
booleans, nulls, and integers, so both implementations produce identical
canonical strings.
X-FITsociety-Webhook-Timestamp, so a timestamp-freshness check does not
reject legitimate retries. If signature verification keeps failing, confirm
that you stored the full one-time signingSecret (including the whsec_
prefix) and that you canonicalize the parsed body instead of hashing the raw
request bytes.
Deliveries and retries
A delivery is successful only when the target returns an HTTP2xx response.
FITsociety stores the response status, a redacted response body preview, and the
last error summary for debugging.
Each delivery has
maxAttempts: 3. Failed attempts are retried with exponential
backoff, starting at 1 minute and then 2 minutes, with a 1 hour cap for future
retry schedules. nextAttemptAt is null after success or exhaustion.
List deliveries
meta.rateLimit when the request passed through the Public API rate limiter.
Delivery list response fields:
Delivery detail response fields: