Skip to main content
Public API webhooks let an integration receive outbound FITsociety events at a public HTTPS endpoint. These endpoints are management resources and use the same OAuth Bearer authentication, scopes, idempotency, response envelope, and rate-limit metadata as other Public API resources.
/public/v1/webhooks, /public/v1/webhooks/{id}, and /public/v1/webhooks/deliveries manage outbound webhook subscriptions. Provider receiver routes below /public/v1/webhooks/*, such as payment provider callbacks or InBody sync triggers, are separate inbound callback routes and keep provider-specific authentication.

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.
Webhook payloads do not include secrets, access tokens, API keys, raw provider responses, request headers, payment provider ids, PDF/download URLs, image or media URLs, invoice line items, note bodies, conversation message text, conversation names, conversation descriptions, context labels, form answers, health notes, free text rejection/reason messages, or internal audit/history arrays.

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

The create response includes 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 HTTP POST with JSON body and these headers: The signature uses the one-time signing secret and this signed string:
The payload JSON is canonicalized by sorting object keys recursively and keeping array order. Receivers should reject stale timestamps and compare the computed signature in constant time. Outbound payloads use this shape:

Verifying webhook signatures

Verify every delivery before processing it:
  1. Read X-FITsociety-Webhook-Timestamp, X-FITsociety-Webhook-Delivery, and X-FITsociety-Webhook-Signature. The event id is the id field of the JSON body; it is not sent as a header.
  2. Reject stale timestamps. The examples below allow 5 minutes of clock skew.
  3. 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.
  4. Rebuild the signed string <timestamp>.<deliveryId>.<eventId>.<canonical_payload_json>, compute HMAC-SHA256 with your stored signing secret, prefix the hex digest with v1=, and compare against the signature header in constant time.
The 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.
Retried deliveries are re-signed at send time with a fresh 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 HTTP 2xx 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

Delivery list and detail responses use the Public API envelope and include meta.rateLimit when the request passed through the Public API rate limiter. Delivery list response fields: Delivery detail response fields: