POST to your endpoint within seconds whenever a court’s
bookable availability changes on any channel — your app, the club’s own tools,
another partner, a class, or an event.
Webhooks are the low-latency signal; the change feed
is the correctness backstop. Build for both (see Staying in sync).
Quickstart
Build a live calendar in five steps:- Baseline. Pull the club grid once to render the calendar and store the returned
cursor:GET /v1/platform/availability?clubId=8&from=…&to=…. - Register an HTTPS endpoint (below) and store the
whsec_…signing secret. - Receive & verify. On each
POST, check the1Club-Signatureheader against the raw body (verify the signature) and respond2xxwithin 10 seconds. - Re-pull. Fetch the affected window named in the event’s
dataand update those slots:GET /v1/platform/areas/{areaId}/availability?from=…&to=…. Keep the highestversion/sequenceyou’ve applied. - Reconcile. On a slow timer — and after any downtime — call
GET /v1/platform/availability/changes?since=<cursor>, apply the deltas, and store the newcursor.
Register an endpoint
In the admin dashboard under Settings - APIs and MCP, add a webhook endpoint: an HTTPS URL to receive events. On creation you get a signing secret (whsec_...) shown once — store it securely; you’ll use it to verify every
delivery. You can rotate the secret or disable the endpoint at any time.
Webhooks are part of the paid API feature. Endpoints must use
https.The availability.changed event
The only event today. Its payload is a thin invalidation: it names the area and
window that changed and why — not the new availability itself. On receipt, re-pull
GET /availability for
that area/window to get the authoritative state. This keeps 1Club the source of truth
and makes events safe to process out of order.
id- unique per event. Deduplicate on it (delivery is at-least-once).sequence- the change’s position in your org’s change feed. Compare it to theversionon an availability read to know whether your last read already reflects this change; use it as thesincecursor for the change feed.data.reason- a coarse cause label, e.g.booking.created,booking.cancelled,booking.updated,booking.released,class.reserved,class.reservation_removed,event.published,event.unpublished,event.cancelled,event.updated,area.updated. Treat it as a hint; always re-pull for truth.
data.window is the affected time range, but for configuration changes (such
as area.updated, when operating hours or capacity change) it is a coarse,
forward-looking window of up to ~60 days — not a single slot. Always re-pull
/availability for the window rather than assuming exactly one slot changed.sequence, version, and cursor
Three related tokens tie reads, webhooks, and the change feed together. All are
monotonically increasing integers sent as strings (they can exceed 2^53, so
don’t parse them into a JS number — compare as strings of equal length, or as
BigInt).
| Token | Scope | Where you see it | Use it to |
|---|---|---|---|
sequence | one change | webhook sequence, change-feed sequence | order changes / detect gaps; pass as the since cursor |
version | one area | availability read (version, and the ETag) | tell whether a read already reflects a given sequence |
cursor | whole org | grid cursor, change-feed cursor | your reconciliation checkpoint |
Verify the signature
Every delivery includes a timestamped HMAC-SHA256 signature over the raw request body, so you can confirm it came from 1Club and reject replays. Headers:| Header | Description |
|---|---|
1Club-Signature | t=<unix-seconds>,v1=<hex> |
1Club-Event-Id | The event id (also in the body) |
1Club-Event-Type | availability.changed |
1Club-Delivery-Attempt | Attempt number, starting at 1 |
v1= element is the signature-scheme version. If a future scheme is added it
will be sent as an extra element (e.g. v2=) alongside v1=, so parse by name and
verify the scheme you support rather than assuming a fixed layout.
The signed string is "<t>.<raw-body>". Verify with a constant-time compare and
reject timestamps outside a tolerance (e.g. 5 minutes):
Verify against the raw request body, before any JSON parsing/re-serialization
— a re-encoded body will not match the signature.
Delivery, retries, and idempotency
- Respond within 10 seconds. Any
2xxmarks the delivery successful. A non-2xx, a connection error, or a response slower than the 10-second timeout counts as a failure. Do the minimum synchronously (verify, enqueue, ack) and re-pull availability asynchronously. - At-least-once. You may receive the same
idmore than once. Deduplicate onidand make handling idempotent (re-pulling availability already is). - Retries with backoff. A failed delivery is retried up to 7 attempts total — the first immediately, then after roughly 1m, 5m, 30m, 2h, 6h, and 24h (about 33 hours end to end). Timing has a few seconds of jitter.
- Auto-disable. After the 7th attempt fails, the endpoint is disabled automatically and the reason is shown under Settings → APIs and MCP. Re-enable it (and rotate the secret if needed) once healthy, then reconcile with the change feed to catch up on everything sent while it was down.
- Ordering is not guaranteed. Use
sequenceif you need to order or detect gaps; because events are invalidations you re-pull for, exact order rarely matters.
Reconcile after downtime
If your endpoint was down (or auto-disabled), don’t try to replay individual webhooks — pull the change feed from your last storedcursor, apply the changes, and store the new cursor. Polling
the feed on a slow timer (e.g. every few minutes) in addition to webhooks means your
calendar self-heals from any missed delivery.
First sync (no cursor yet): do the baseline grid pull (Quickstart
step 1) and start from the cursor it returns — don’t start from 0 for a live
club, or you’ll replay the entire history.
Very long outages: the feed serves a rolling history. If your stored cursor
is older than the retained history, the feed can’t close the whole gap — discard
the cursor and re-baseline with a full grid pull, exactly like first sync. When in
doubt, a full grid re-pull is always a safe way to resynchronise.
Troubleshooting
- Deliveries stopped arriving. The endpoint was likely auto-disabled after repeated failures — check Settings → APIs and MCP for the disable reason, fix your endpoint, re-enable it, then reconcile from your last
cursor. - Signature always fails. You’re almost certainly hashing a re-serialized body — verify against the exact bytes received (see the note above), and confirm you’re using the current secret (rotating it invalidates the old one immediately).
- Correlate and dedupe on your side. Log the
1Club-Event-Idand1Club-Delivery-Attemptheaders: the event id lets you dedupe, and the attempt number tells first deliveries apart from retries.