Skip to main content
Webhooks push availability changes to your app in near real time, so you can show a live calendar instead of discovering conflicts only when a booking is rejected. 1Club sends a signed HTTP 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:
  1. Baseline. Pull the club grid once to render the calendar and store the returned cursor: GET /v1/platform/availability?clubId=8&from=…&to=….
  2. Register an HTTPS endpoint (below) and store the whsec_… signing secret.
  3. Receive & verify. On each POST, check the 1Club-Signature header against the raw body (verify the signature) and respond 2xx within 10 seconds.
  4. Re-pull. Fetch the affected window named in the event’s data and update those slots: GET /v1/platform/areas/{areaId}/availability?from=…&to=…. Keep the highest version/sequence you’ve applied.
  5. Reconcile. On a slow timer — and after any downtime — call GET /v1/platform/availability/changes?since=<cursor>, apply the deltas, and store the new cursor.
Steps 3–4 keep the calendar fresh in seconds; steps 1 and 5 keep it correct even if a webhook is never delivered.

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 the version on an availability read to know whether your last read already reflects this change; use it as the since cursor 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).
TokenScopeWhere you see itUse it to
sequenceone changewebhook sequence, change-feed sequenceorder changes / detect gaps; pass as the since cursor
versionone areaavailability read (version, and the ETag)tell whether a read already reflects a given sequence
cursorwhole orggrid cursor, change-feed cursoryour 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:
HeaderDescription
1Club-Signaturet=<unix-seconds>,v1=<hex>
1Club-Event-IdThe event id (also in the body)
1Club-Event-Typeavailability.changed
1Club-Delivery-AttemptAttempt number, starting at 1
The 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 2xx marks 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 id more than once. Deduplicate on id and 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 sequence if 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 stored cursor, 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-Id and 1Club-Delivery-Attempt headers: the event id lets you dedupe, and the attempt number tells first deliveries apart from retries.